在短时间窗口内,当属性值多次更改时,DataTrigger不触发。

6
我在我的WPF应用程序中发现了一个有趣的问题,与MultiDataTrigger相关,无法启动StoryBoard以使数据网格单元格动画化。我有一个WPF数据网格控件,它绑定到一个实现了INotifyPropertyChanged的POCO集合的ObservableCollection。
我想要实现的是一个实时数据网格,当值改变时闪烁更新。当值增加时,我希望单元格闪烁为绿色;当值减小时,我希望单元格闪烁为红色。该动画仅将单元格的背景颜色从纯色渐变为透明,持续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是否只忽略从一个值更改到另一个值然后再次更改回原始值的更改,而在一定时间内忽略此类更改?

感谢任何帮助!


如果在同一调度程序操作中值再次更改,则WPF可能会忽略更改。请尝试分派HasValueChanged=true部分,而不是休眠。 - Mårten Wikström
1个回答

0

像事件一样使用属性似乎比Thread.Sleep更糟糕,我建议使用两个RoutedEvents(以区分Changed+PositiveChanged+Negative),并与EventTriggers结合使用。


如果你想使用 Thread.Sleep,最好在后台执行:
// IDE-free code, may be broken
HasValueChanged = false;
new Thread((ThreadStart)(() =>
{
    Thread.Sleep(100);
    HasValueChanged = true;
})).Start();

我真的不想使用Thread.Sleep,所以您的第一个建议听起来像是正确的方向。但是有一个问题:我可以在不继承自UIElement的类上触发RoutedEvents吗?Cell类是一个独立的Poco类,不在视觉树中。 - Lee F
没错,这可能是个问题。如果您使用普通事件,您无法使用默认的EventTrigger,来自Blend互动性的EventTriggers可以与每个事件一起使用,但在样式中使用它们可能会相当困难... - H.B.
在MVVM应用程序中,使用数据触发器来监视属性变化是更可取的。你知道怎么做吗? - Gusdor
@Gusdor:不过,你不应该有这样的属性。 - H.B.
我不知道为什么不能这样做。毕竟,WPF绑定CLR属性后钩入OnPropertyChanged事件。它是一个描述USB设备是否连接的布尔值-似乎是公开状态的合理方式。然而,我已经解决了我的问题。数据触发器在任何时候都可以正常触发,但在加载后不会触发。我使用带有条件行为的事件触发器在加载时应用正确的视觉状态。 - Gusdor

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接