【问题标题】:DataTrigger not firing when property value changes multiple times within a short window当属性值在短窗口内多次更改时,DataTrigger 不会触发
【发布时间】:2011-09-15 02:17:39
【问题描述】:

我在我的 WPF 应用程序中发现了一个有趣的问题,该问题与 MultiDataTrigger 没有启动 StoryBoard 来为数据网格单元格设置动画有关。我有一个 WPF 数据网格控件,它绑定到包含实现 INotifyPropertyChanged 的​​ POCO 的 ObservableCollection。

我想要达到的目标

实时数据网格会随着值的变化而更新。当值增加时,我希望单元格闪烁绿色;当值减小时,我希望单元格闪烁红色。动画只是在 1 秒的时间间隔内将单元格的背景颜色从纯色变为透明。

问题

在第一次之后,MultiDataTrigger 未启动情节提要。 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 对象的代码:

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 不触发的问题,但会产生不希望的副作用。

问题视频

点击here查看破解版视频。

点击here查看修复版视频。

“固定”版本并不理想,因为 Thread.Sleep 导致单元格以明显顺序的方式更新,而不是像损坏的版本那样同时更新。此外,里面有一个 Thread.Sleep 让我感觉糟糕 :)

首先;我做错了吗?有没有更简单/更好的方法来实现我想要的?如果没有,有什么方法可以解决这个问题而无需添加 Thread.Sleep(代码异味!)?

当值快速更改时,为什么 WPF 不会触发 DataTrigger?是否存在导致 WPF“错过”属性更改的原因?还是 WPF 只是忽略从一个值到另一个值然后在特定时间范围内返回到原始值的更改?

感谢您的帮助!

【问题讨论】:

  • 如果值在同一个调度程序操作中再次变回,WPF 可能会忽略该更改。尝试调度HasValueChanged=truepart,而不是睡觉。

标签: c# wpf xaml animation datatrigger


【解决方案1】:

对我来说,使用类似事件的属性似乎比 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。
  • 对,这可能是个问题。如果您使用普通事件,则不能使用默认的 EventTrigger,Blend Interactivity 中的 EventTriggers 将适用于每个事件,但在样式中使用它们可能会非常困难......
  • 在 MVVM 应用程序中最好对更改的属性使用数据触发器。知道你会怎么做吗?
  • @Gusdor:你不应该有这样的财产。
  • 我不明白为什么不这样做。毕竟,CLR 属性上的 WPF 绑定与 OnPropertyChanged 事件挂钩。它是一个布尔值,用于描述是否连接了 USB 设备 - 公开状态似乎是合理的。但是,我对我的问题进行了分类。数据触发器始终正常触发,但加载后不会触发。我使用了带有条件行为的事件触发器来在加载时应用正确的视觉状态。
猜你喜欢
  • 2012-01-31
  • 2017-11-06
  • 1970-01-01
  • 2015-10-20
  • 2023-01-14
相关资源
最近更新 更多