在我的一个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,但我还没有找到一个。是吗,还是我只是在做梦?
答案 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的FallTime
和TotalTime
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)。之后,OnRaiseTimeChanged
和OnFallTimeChanged
会更新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
}