В моем приложении WPF обнаружена интересная проблема, связанная с тем, что MultiDataTrigger не запускает StoryBoard для анимации ячейки сетки данных. У меня есть элемент управления сетки данных WPF, который связан с ObservableCollection, содержащей POCO, которые реализуют INotifyPropertyChanged.
Чего я хочу достичь
Сетка данных в реальном времени, которая мигает, обновляется при изменении значений. Когда значение увеличивается, я хочу, чтобы ячейка мигала зеленым; когда значение уменьшается, я хочу, чтобы ячейка мигала красным. Анимация просто анимирует цвет фона ячейки от сплошного до прозрачного в течение 1 секунды.
Проблема
Раскадровка не запускается MultiDataTrigger после первого раза. MultiDataTrigger отслеживает изменения двух свойств: IsPositive и HasValueChanged. Первоначально HasValueChanged имеет значение false, а затем изменяется с false на true, как только свойство Value установлено и анимация работает в первый раз. После этого 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, который связан с каждой ячейкой:
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); }
}
}
}
Видимое исправление
Добавление Thread.Sleep (100) между установками HasValueChanged дважды в методе SetValue, по-видимому, устраняет проблему не запускающего MultiDataTrigger, но имеет нежелательные побочные эффекты.
Видео проблемы
Нажмите здесь , чтобы посмотреть видео сломанной версии.
Нажмите здесь , чтобы посмотреть видео с фиксированной версией.
«Фиксированная» версия не идеальна, потому что Thread.Sleep заставляет ячейки обновляться явно последовательным образом, а не одновременно, как в сломанной версии. Кроме того, наличие Thread.Sleep там заставляет меня чувствовать себя плохо :)
Прежде всего; я все об этом ошибаюсь? Есть ли более простой / лучший способ добиться того, чего я хочу? Если нет, каково решение этой проблемы, не прибегая к добавлению Thread.Sleep (запах кода!)?
Почему WPF не запускает DataTrigger при быстром изменении значений? Есть ли что-то, что заставляет WPF «пропустить» изменения свойств; или WPF просто игнорирует изменения, которые переходят от одного значения к другому, а затем возвращаются к исходному значению в течение определенного периода времени?
Спасибо за любую помощь!