有没有一种方法将xaml绑定到依赖于多个其他属性的属性?

时间:2013-01-10 17:49:34

标签: c# wpf xaml dependency-properties

在我的一个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问题和博客文章: Bind an element to two sources http://blog.wpfwonderland.com/2010/04/15/simplify-your-binding-converter-with-a-custom-markup-extension/

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

4 个答案:

答案 0 :(得分:4)

你绝对可以使用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; }
    }
}

答案 1 :(得分:1)

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

将“OnChanged”回调附加到更新RaiseTime DP的FallTimeTotalTime DP,并使TotalTime成为只读(不同的DP注册语法,只有私有的setter) :

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

答案 2 :(得分:0)

正如您的链接所示,MultiBinding是此标准方式。

如果您想要晦涩难懂的解决方案,可以创建custom markup extension

答案 3 :(得分: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
}