DataTrigger не запускается, когда значение свойства изменяется в течение короткого окна несколько раз - PullRequest
6 голосов
/ 15 июня 2011

В моем приложении 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 просто игнорирует изменения, которые переходят от одного значения к другому, а затем возвращаются к исходному значению в течение определенного периода времени?

Спасибо за любую помощь!

1 Ответ

0 голосов
/ 15 июня 2011

Использование свойства, подобного событию, кажется мне более грубым, чем Thread.Sleep, я бы предложил использовать два RoutedEvents (чтобы различать Changed+Positive & Changed+Negative) в сочетании с EventTriggers.


Если вы хотите придерживаться Thread.Sleep, вы, вероятно, должны сделать это на заднем плане:

// IDE-free code, may be broken
HasValueChanged = false;
new Thread((ThreadStart)(() =>
{
    Thread.Sleep(100);
    HasValueChanged = true;
})).Start();
...