我在我的WPF应用程序中发现了一个有趣的问题,与MultiDataTrigger相关,无法启动StoryBoard以使数据网格单元格动画化。我有一个WPF数据网格控件,它绑定到一个实现了INotifyPropertyChanged的POCO集合的ObservableCollection。
我想要实现的是一个实时数据网格,当值改变时闪烁更新。当值增加时,我希望单元格闪烁为绿色;当值减小时,我希望单元格闪烁为红色。该动画仅将单元格的背景颜色从纯色渐变为透明,持续1秒钟。
问题是,MultiDataTrigger在第一次后不会启动Storyboard。MultiDataTrigger监视两个属性的更改:IsPositive和HasValueChanged。最初,HasValueChanged为false,稍后在设置Value属性后从false更改为true,并且动画在第一次起作用。此后,HasValueChanged从false到true脉冲以触发更改通知,但动画未启动。
这是我应用于每个数据网格单元格的XAML样式:
这里是动画的XAML代码:
这是与每个单元格绑定的POCO对象的代码:
```html
我想要实现的是一个实时数据网格,当值改变时闪烁更新。当值增加时,我希望单元格闪烁为绿色;当值减小时,我希望单元格闪烁为红色。该动画仅将单元格的背景颜色从纯色渐变为透明,持续1秒钟。
问题是,MultiDataTrigger在第一次后不会启动Storyboard。MultiDataTrigger监视两个属性的更改:IsPositive和HasValueChanged。最初,HasValueChanged为false,稍后在设置Value属性后从false更改为true,并且动画在第一次起作用。此后,HasValueChanged从false到true脉冲以触发更改通知,但动画未启动。
这是我应用于每个数据网格单元格的XAML样式:
<Style TargetType="{x:Type TextBlock}">
<Style.Setters>
<Setter Property="Background"
Value="Aqua" />
</Style.Setters>
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding Path=HasValueChanged}"
Value="True" />
<Condition Binding="{Binding Path=IsPositive}"
Value="True" />
</MultiDataTrigger.Conditions>
<MultiDataTrigger.EnterActions>
<RemoveStoryboard BeginStoryboardName="PositiveValueCellStoryboard" />
<RemoveStoryboard BeginStoryboardName="NegativeValueCellStoryboard" />
<BeginStoryboard Name="PositiveValueCellStoryboard"
Storyboard="{StaticResource PositiveValueCellAnimation}"
HandoffBehavior="SnapShotAndReplace" />
</MultiDataTrigger.EnterActions>
</MultiDataTrigger>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding Path=HasValueChanged}"
Value="True" />
<Condition Binding="{Binding Path=IsPositive}"
Value="False" />
</MultiDataTrigger.Conditions>
<MultiDataTrigger.EnterActions>
<RemoveStoryboard BeginStoryboardName="PositiveValueCellStoryboard" />
<RemoveStoryboard BeginStoryboardName="NegativeValueCellStoryboard" />
<BeginStoryboard Name="NegativeValueCellStoryboard"
Storyboard="{StaticResource NegativeValueCellAnimation}"
HandoffBehavior="SnapShotAndReplace" />
</MultiDataTrigger.EnterActions>
</MultiDataTrigger>
</Style.Triggers>
</Style>
这里是动画的XAML代码:
<Storyboard x:Key="NegativeValueCellAnimation">
<ColorAnimation Storyboard.TargetProperty="Background.(SolidColorBrush.Color)"
Timeline.DesiredFrameRate="10"
RepeatBehavior="1x"
From="Red"
To="Transparent"
Duration="0:0:1" />
</Storyboard>
<Storyboard x:Key="PositiveValueCellAnimation">
<ColorAnimation Storyboard.TargetProperty="Background.(SolidColorBrush.Color)"
Timeline.DesiredFrameRate="10"
RepeatBehavior="1x"
From="Green"
To="Transparent"
Duration="0:0:1" />
</Storyboard>
这是与每个单元格绑定的POCO对象的代码:
```html
这里是与每个单元格绑定的POCO对象的代码:
```using System;
using System.Threading;
using Microsoft.Practices.Prism.ViewModel;
namespace RealTimeDataGrid
{
public class Cell : NotificationObject
{
public Cell(int ordinal, int value)
{
Ordinal = ordinal;
_value = value;
LastUpdated = DateTime.MaxValue;
}
public void SetValue(int value)
{
Value = value;
// Pulse value changed to get WPF to fire DataTriggers
HasValueChanged = false;
Thread.Sleep(100);
HasValueChanged = true;
}
private int _value;
public int Value
{
get { return _value; }
private set
{
if (_value == value)
return;
_value = value;
// Performance optimization, using lambdas here causes performance issues
RaisePropertyChanged("IsPositive");
RaisePropertyChanged("Value");
}
}
private bool _hasValueChanged;
public bool HasValueChanged
{
get { return _hasValueChanged; }
set
{
if (_hasValueChanged == value)
return;
_hasValueChanged = value;
// Performance optimization, using lambdas here causes performance issues
RaisePropertyChanged("HasValueChanged");
}
}
public int Ordinal { get; set; }
public DateTime LastUpdated { get; set; }
public bool IsPositive
{
get { return Value >= 0; }
}
public TimeSpan TimeSinceLastUpdate
{
get { return DateTime.Now.Subtract(LastUpdated); }
}
}
}
表面上的修复
在SetValue方法中设置HasValueChanged两次之间添加Thread.Sleep(100)似乎可以解决MultiDataTrigger不触发的问题,但会带来不良副作用。
问题视频
点击这里查看故障版本的视频。
点击这里查看已修复版本的视频。
已修复版本并不理想,因为Thread.Sleep导致单元格更新表现为一种表面上的连续方式,而非像故障版本那样同时进行。此外,在其中添加Thread.Sleep让我感觉很糟糕 :)
首先;我的做法是有误的吗?是否有更简单、更好的方法来实现我想要的?如果没有,如何解决这个问题而不必添加Thread.Sleep(代码异味!)?
为什么WPF在值快速更改时不会触发DataTrigger?是什么导致WPF“忽略”属性更改?或者WPF是否只忽略从一个值更改到另一个值然后再次更改回原始值的更改,而在一定时间内忽略此类更改?
感谢任何帮助!
HasValueChanged=true
部分,而不是休眠。 - Mårten Wikström