c# - 时间线中可调整大小的片段 : bind segment size to view model

标签 c# xaml timeline avaloniaui

我正在尝试实现一个用户控件,该控件代表时间线(例如在视频编辑器中),其中的片段的开始和结束标记可以由用户拖动。

我在我的 View 模型中表示这样的片段:

public class Moment : ViewModelBase
{
    [Reactive] public double From { get; set; }
    [Reactive] public double Duration { get; set; }
}

并尝试使用带有网格分割器的网格来实现 View ,如下所示:

<Grid ColumnDefinitions="Auto,3,Auto,3,*" 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>

这就像我可以在视觉上调整片段的大小一样:

timeline drag example gif

但是,我正在努力将尺寸更改返回到 View 模型中,您也可以在红色区域中看到尺寸没有改变。我找不到方法来检索 SpacerLeftSpacerSegment 控件的宽度更改。

我尝试的是删除专用的Width属性并绑定(bind)网格的ColumnDefinitions。我将此属性添加到我的 View 模型中:

public ColumnDefinitions ColumnDefinitions
{
    get => ColumnDefinitions.Parse($"{From},3,{Duration},3,*");
    set
    {
        From = value[0].ActualWidth;
        Duration = value[2].ActualWidth;
    }
}

并将 View 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
  ...

最佳答案

第一个问题是尝试直接绑定(bind)到Grid的ColumnDefinitions。相反,使用长定义形式并直接绑定(bind)到每列的宽度是正确的方法:

<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="{Binding ...}" />
        <ColumnDefinition Width="Auto" />
        <!-- ... -->
    </Grid.ColumnDefinitions>
</Grid>

还需要显式指定 Mode=TwoWay,因为默认情况下,绑定(bind)到 ColumnDefinition 的宽度不是双向的。

接下来,由于 ColumnDefinitionWidth 属性实际上是 GridLength 类型,而不是 double,因此如所述 here 需要 IValueConverter .

此时网格的 XAML 应该如下所示

<Grid HorizontalAlignment="Stretch">
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="{Binding From, Mode=TwoWay, Converter={StaticResource GridLengthConverter}}" />
        <ColumnDefinition Width="Auto" />
        <ColumnDefinition Width="{Binding Duration, Mode=TwoWay, 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 View 模型需要使用 FromTo 属性而不是 FromDuration 属性,并让 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;
}

经过这些更改,结果如下所示:

segment slider demo

关于c# - 时间线中可调整大小的片段 : bind segment size to view model,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/67257867/

相关文章:

c# - 我可以在 C# 中找到 BigInteger 的位数吗?

c# - Entity Framework 将数千个对象传输到 Elastic Search

c# - 在图片上添加点击事件

c# - WPF 上下文相关帮助 - 最佳实践

limit - 如何使用处理和 Twitter4j 从用户时间轴获取 20 多个结果?

javascript - 使用 Javascript/jQuery 创建垂直时间轴

c# - 安装 DotNET 4.5 时,SvcUtil/edb 不会生成 INotifyPropertyChange

c# - 根据C#中的日期从Xml中过滤数据

wpf - 使用 WPF 和 MVVM 编辑 F# 记录

html - Angularjs 时间轴事件放置