3

在我的一个 WPF 项目中,我在 XAML 中创建了一个动画故事板,它具有可以在开始动画之前动态更改的计时属性。由于我需要一种方法来更改代码中的值,因此我将它们绑定到类的属性。

基本思想是动画有两个阶段,在情节提要中,我使用 ObjectAnimationUsingKeyFrames ,它占用了总动画时间,所以我有这样的属性:

public TimeSpan RaiseTime { get; set }

public TimeSpan FallTime  { get; set; }

public TimeSpan TotalTime
{ 
    get { return RaiseTime + FallTime; }
}

首次创建动画时,它会正确地从这些属性中获取值,但由于它们可以动态更改,我需要一种方法来通知 XAML 这些值已更改。

将 RaiseTime 和 FallTime 转换为 DependencyPropertys 很容易,以便它们的更改将反映在 XAML 绑定中,但是 TotalTime 呢?它本身没有价值,所以我不能把它变成 DP。

昨天我花了几个小时搜索/尝试随机的东西来尝试让它工作并最终使用 MultiBinding 到 RaiseTime 和 FallTime 以及 IMultiValueConverter 来工作,这要归功于一些 SO 问题和博客文章: 将元素绑定到两个来源 http://blog.wpfwonderland.com/2010/04/15/simplify-your-binding-converter-with-a-custom-markup-extension/

我的问题是:这真的是最好的方法吗?这似乎(至少在我看来)是一个如此简单的任务,但它需要这么多(主要是样板)代码才能开始工作。我认为必须有一种更简单、更简洁的方法来绑定 TotalTime 并将更新推送到 XAML,但我还没有找到。有吗,还是我只是在做梦?

4

4 回答 4

6

您绝对可以使用 INotifyPropertyChanged 使其工作,然后简单地绑定到该类。

代码看起来像这样:(未经测试的代码)

public class PleaseChangeTheNameOfThisClass : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    public void RaisePropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    private TimeSpan _raiseTime;
    public TimeSpan RaiseTime
    { 
        get { return _raiseTime; }
        set 
        {
            if (_raiseTime != value)
            {
                _fallTime = value;
                RaisePropertyChanged("RaiseTime");
                RaisePropertyChanged("TotalTime");
            }
        }
    }

    private TimeSpan _fallTime;
    public TimeSpan FallTime  
    { 
        get { return _fallTime; }
        set 
        {
            if (_fallTime != value)
            {
                _fallTime = value;
                RaisePropertyChanged("FallTime");
                RaisePropertyChanged("TotalTime");
            }
        }
    }

    public TimeSpan TotalTime
    { 
        get { return RaiseTime + FallTime; }
    }
}
于 2013-01-10T18:04:58.380 回答
1

它可能仍然很冗长,但实际上您可以使用 DependencyProperties 执行此操作:

将“OnChanged”回调附加到更新 DP 的DPRaiseTime和DP,并使 TotalTime 只读(不同的 DP 注册语法,并且只有一个私有设置器):FallTimeTotalTime

public TimeSpan RaiseTime
{
    get { return (TimeSpan)GetValue(RaiseTimeProperty); }
    set { SetValue(RaiseTimeProperty, value); }
}
public static readonly DependencyProperty RaiseTimeProperty =
    DependencyProperty.Register("RaiseTime", typeof(TimeSpan), typeof(MainWindow),
                                new PropertyMetadata(TimeSpan.Zero, OnRaiseTimeChanged));

private static void OnRaiseTimeChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
    var owner = sender as MainWindow;
    owner.TotalTime = owner.RaiseTime + owner.FallTime;
}


public TimeSpan FallTime
{
    get { return (TimeSpan)GetValue(FallTimeProperty); }
    set { SetValue(FallTimeProperty, value); }
}
public static readonly DependencyProperty FallTimeProperty =
    DependencyProperty.Register("FallTime", typeof(TimeSpan), typeof(MainWindow),
                                new PropertyMetadata(TimeSpan.Zero, OnFallTimeChanged));

private static void OnFallTimeChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
    var owner = sender as MainWindow;
    owner.TotalTime = owner.RaiseTime + owner.FallTime;
}


/// <summary>
/// Read-only DP:
/// http://msdn.microsoft.com/en-us/library/ms754044.aspx
/// http://www.wpftutorial.net/dependencyproperties.html
/// </summary>
public TimeSpan TotalTime
{
    get { return (TimeSpan)GetValue(TotalTimeProperty); }
    private set { SetValue(TotalTimePropertyKey, value); }
}
public static readonly DependencyPropertyKey TotalTimePropertyKey =
    DependencyProperty.RegisterReadOnly("TotalTime", typeof(TimeSpan), typeof(MainWindow),
                                        new PropertyMetadata(TimeSpan.Zero));

public static readonly DependencyProperty TotalTimeProperty = TotalTimePropertyKey.DependencyProperty;

默认值需要相加(这里:0 + 0 = 0)。之后,OnRaiseTimeChangedOnFallTimeChanged不断TotalTime更新。

于 2013-01-10T22:55:33.760 回答
0

我认为这再次简洁一些。请参阅最新 C# 中提供的“CallerMemberName”。

public sealed class ViewModel : INotifyPropertyChanged
{
    private TimeSpan _raiseTime;
    private TimeSpan _fallTime;

    public TimeSpan RaiseTime
    {
        get { return _raiseTime; }
        private set
        {
            if (SetProperty(ref _raiseTime, value))
            {
                OnPropertyChanged("TotalTime");
            }
        }
    }

    public TimeSpan FallTime
    {
        get { return _fallTime; }
        private set
        {
            if (SetProperty(ref _fallTime, value))
            {
                OnPropertyChanged("TotalTime");
            }
        }
    }

    public TimeSpan TotalTime
    {
        get { return RaiseTime + FallTime; }
    }

    #region Put these in a base class...

    private bool SetProperty<T>(ref T storage, T value, [CallerMemberName] String propertyName = null)
    {
        if (Equals(storage, value))
        {
            return false;
        }

        storage = value;
        OnPropertyChanged(propertyName);
        return true;
    }

    public event PropertyChangedEventHandler PropertyChanged;

    private void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    #endregion
}
于 2013-01-30T13:59:34.477 回答
0

正如您的链接所暗示的,MultiBinding是标准的方法。

如果您想要模糊的解决方案,您可以创建自定义标记扩展

于 2013-01-10T17:52:18.437 回答