问题描述
我正在尝试实现一个用户控件,该控件代表一个时间轴(如在视频编辑器中),其中包含一个片段,该片段的开始和结束标记可由用户拖动。
我在我的视图模型中代表一个这样的段:
public class Moment : viewmodelBase
{
[Reactive] public double From { get; set; }
[Reactive] public double Duration { get; set; }
}
并尝试使用带有网格拆分器的网格来实现视图,如下所示:
<Grid ColumnDeFinitions="Auto,3,Auto,*" HorizontalAlignment="Stretch">
<Panel Grid.Column="0" Name="SpacerLeft"
Width="{Binding From}" />
<GridSplitter Grid.Column="1" Background="cyan" />
<Rectangle Grid.Column="2" Name="SpacerSegment"
HorizontalAlignment="Stretch" Fill="red" Height="40"
Width="{Binding Duration}">
</Rectangle>
<GridSplitter Grid.Column="3" Background="cyan" />
<Panel Grid.Column="4" Name="SpacerRight"/>
</Grid>
这有效,因为我可以在视觉上稍微调整段的大小:
但是,我正在努力将大小更改返回到视图模型中,您也可以在未更改大小的红色区域中看到该更改。我找不到检索 SpacerLeft
或 SpacerSegment
控件的宽度变化的方法。
我尝试的是删除专用的 Width
属性,而是绑定网格的 ColumnDeFinitions
。我将此属性添加到我的视图模型中:
public ColumnDeFinitions ColumnDeFinitions
{
get => ColumnDeFinitions.Parse($"{From},{Duration},*");
set
{
From = value[0].ActualWidth;
Duration = value[2].ActualWidth;
}
}
并将视图 XAML 更改为:
<Grid ColumnDeFinitions="{Binding ColumnDeFinitions}" HorizontalAlignment="Stretch">
<Panel Grid.Column="0" Name="SpacerLeft" />
<GridSplitter Grid.Column="1" Background="cyan" />
<Rectangle Grid.Column="2" Name="SpacerSegment"
HorizontalAlignment="Stretch" Fill="red" Height="40">
</Rectangle>
<GridSplitter Grid.Column="3" Background="cyan" />
<Panel Grid.Column="4" Name="SpacerRight"/>
</Grid>
但是由于一个对我来说没有多大意义的原因,这无法编译:
InvalidCastException: Unable to cast object of type 'Avalonia.Data.Binding' to type 'Avalonia.Controls.ColumnDeFinition'. system.invalidCastException: Unable to cast object of type 'Avalonia.Data.Binding' to type 'Avalonia.Controls.ColumnDeFinition'.
at Avalonia.Collections.AvaloniaList`1.System.Collections.IList.Add(Object value) in /_/src/Avalonia.Base/Collections/AvaloniaList.cs:line 520
at Builder_1ee6d795025442edb279bcc7110e88eb_avares://AvaloniaOutseekClient/Views/MomentsSourceView.axaml.XamlClosure_2.Build(IServiceProvider )
at Avalonia.Markup.Xaml.XamlIl.Runtime.XamlIlRuntimeHelpers.<>c__displayClass0_0.<DeferredTransformationFactoryV1>b__0(IServiceProvider sp) in /_/src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/XamlIlRuntimeHelpers.cs:line 28
at Avalonia.Markup.Xaml.Templates.TemplateContent.Load(Object templateContent) in /_/src/Markup/Avalonia.Markup.Xaml/Templates/TemplateContent.cs:line 17
at Avalonia.Markup.Xaml.Templates.DataTemplate.Build(Object data,IControl existing) in /_/src/Markup/Avalonia.Markup.Xaml/Templates/DataTemplate.cs:line 33
at Avalonia.Controls.Presenters.ContentPresenter.CreateChild() in /_/src/Avalonia.Controls/Presenters/ContentPresenter.cs:line 356
...
解决方法
第一个问题是试图直接绑定到 Grid 的 ColumnDefinitions
。相反,使用长定义形式并直接绑定到每列的宽度是要走的路:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{Binding ...}" />
<ColumnDefinition Width="Auto" />
<!-- ... -->
</Grid.ColumnDefinitions>
</Grid>
还需要明确指定 Mode=TwoWay
,因为默认情况下 ColumnDefinition
的宽度绑定不是双向的。
接下来,由于 ColumnDefinition
的 Width
属性实际上属于 GridLength
类型而不是 double
,因此需要 IValueConverter
,如{{3 }}。
此时网格的 XAML 应如下所示
<Grid HorizontalAlignment="Stretch">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{Binding From,Mode=TwoWay,Converter={StaticResource GridLengthConverter}}" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="{Binding Duration,Converter={StaticResource GridLengthConverter}}" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Panel Grid.Column="0" Name="SpacerLeft" />
<GridSplitter Grid.Column="1" Background="cyan" />
<Rectangle Grid.Column="2" Name="SpacerSegment"
HorizontalAlignment="Stretch" Fill="red" Height="40">
</Rectangle>
<GridSplitter Grid.Column="3" Background="cyan" />
<Panel Grid.Column="4" Name="SpacerRight"/>
</Grid>
此外,要修复左网格拆分器移动选择而不是将其扩展到左侧,Moment
视图模型需要使用 From
和 To
属性来表示段From
和 Duration
属性,并且 Duration
属性派生自其他两个属性。这需要手动连接一些属性更改事件,而不是依赖于 [Reactive]
:
private double _from;
private double _to;
public double From
{
get => _from;
set
{
if (Equals(_from,value))
return;
this.RaisePropertyChanging(nameof(Duration));
this.RaisePropertyChanging();
_from = value;
this.RaisePropertyChanged();
this.RaisePropertyChanged(nameof(Duration));
}
}
public double To
{
get => _to;
set
{
if (Equals(_to,value))
return;
this.RaisePropertyChanging(nameof(Duration));
this.RaisePropertyChanging();
_to = value;
this.RaisePropertyChanged();
this.RaisePropertyChanged(nameof(Duration));
}
}
public double Duration
{
get => To - From;
set => To = From + value;
}
通过这些更改,结果如下所示: