WPF DataTrigger 动画只触发一次

Posted

技术标签:

【中文标题】WPF DataTrigger 动画只触发一次【英文标题】:WPF DataTrigger Animation only fires once 【发布时间】:2021-11-28 13:00:53 【问题描述】:

我有一个 MVVM 模式应用程序,我希望用户能够在其中输入日期,但也可以在这些日期上应用一些验证。我通过检查他们输入的任何内容并用最近的有效日期覆盖它来做到这一点,如果他们的输入无效。为了让用户知道他们的日期已被覆盖,我会尝试为日期选择器文本框的前景设置动画,但我发现动画仅在他们的日期第一次以这种方式“更正”时可见.

在 MainViewModel 中,我有一个 Ping 属性,每次设置为“true”时都会通知 UI,还有一个验证方法,每次必须覆盖日期时设置 Ping = true

public bool Ping

    get => _ping;
    set
    
        if (value && !_ping)
        
            _ping = value;
            OnPropertyChanged();
            _ping = false;
        
    


private DateTime _from;

//Bound to the Date input field in the UI
public DateTime From

    get  return _from; 
    set
    
        if (_from != value)
        
            _from = giveValidDate("From", value);
            OnPropertyChanged();
        
    


private DateTime giveValidDate(string posn, DateTime givenDate)

    DateTime validDate = new DateTime();
    // [...A Load of validation that results in a valid Date output...] //

    Ping = givenDate != validDate;

    return validDate;

我正在使用一种带有动画的 TextBox 样式:

<Style x:Key="PingableTextBox" TargetType="TextBox">
    <Setter Property="TextBlock.FontSize" Value="18"/>
    <Setter Property="TextElement.FontSize" Value="18"/>
    <Setter Property="TextElement.Foreground" Value="StaticResource Text_LightBrush"/>
    <Setter Property="TextElement.FontWeight" Value="Normal"/>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="TextBox">
                <Border BorderThickness="TemplateBinding Border.BorderThickness"
                        CornerRadius="2"
                        BorderBrush="StaticResource Highlight_LightBrush"
                        Background="StaticResource Empty_DarkBrush"
                        x:Name="border"
                        SnapsToDevicePixels="True">
                    <ScrollViewer HorizontalScrollBarVisibility="Hidden" VerticalScrollBarVisibility="Hidden"
                                  Name="PART_ContentHost" Focusable="False" />
                </Border>
                <ControlTemplate.Triggers>
                    <Trigger Property="UIElement.IsMouseOver" Value="True">
                        <Setter Property="Border.BorderBrush" TargetName="border" Value="StaticResource Good_MidBrush"/>
                        <Setter Property="Cursor" Value="IBeam"/>
                    </Trigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
    <Style.Triggers>
        <DataTrigger Binding="Binding Ping" Value="true">
            <DataTrigger.EnterActions>
                <StopStoryboard BeginStoryboardName="Pinger"/>
                <BeginStoryboard Name="Pinger">
                    <Storyboard>
                        <ColorAnimation Storyboard.TargetProperty="Foreground.Color"
                                        From="StaticResource Bad_Bright" To="StaticResource Text_Light" FillBehavior="Stop"
                                        Duration="0:0:0:1.0"/>
                    </Storyboard>
                </BeginStoryboard>
            </DataTrigger.EnterActions>
            <DataTrigger.ExitActions>
                <RemoveStoryboard BeginStoryboardName="Pinger"/>
            </DataTrigger.ExitActions>
        </DataTrigger>
    </Style.Triggers>
</Style>

但是,当我运行该应用程序时,触发器只能看到一次(选择无效日期时短暂的红色闪烁):

我在 Stack Overflow 上看到过很多其他关于同一问题的问题,但解决方案一直是在 Enter Actions 中添加行 &lt;StopStoryboard BeginStoryboardName="Pinger"/&gt;,在 Exit Actions 中添加行 &lt;RemoveStoryboard BeginStoryboardName="Pinger"/&gt; 或添加 @ 987654332@ 到故事板。我已经在我能想到的所有地方尝试了这些中的每一种组合,但问题仍然存在。

对于我可能错过的问题是否有其他解释可以为我解决它,或者我未能正确实施的问题。简而言之,为什么它只触发一次?

PS - 我用来实现您在上面看到的代码的一些问题:

WPF Storyboard only fires once

WPF Fade In / Out only runs once

WPF MultiDataTrigger on Tag property only firing once

WPF - problem with DataTrigger which work only once

【问题讨论】:

【参考方案1】:

您必须在重置Ping 属性后引发PropertyChanged 才能触发Trigger.ExitAction。 设置支持字段 _ping 不会将任何更改通知传播到视图。 这意味着您的问题不是您引用的答案试图解决的问题。

您也不应该在您的场景中定义Trigger.ExitAction。由于您已将动画配置为在时间线完成后自动停止 (FillBehavior="Stop"),因此您无需执行任何操作来停止它。请注意,RemoveStoryboard 对您的情况没有任何帮助。它只会使重置属性的逻辑复杂化,因为RemoveStoryboard 会在即时属性切换时立即终止动画。这意味着,避免 Trigger.ExitAction 或更准确地说是 RemoveStoryboard 允许您立即切换属性:

// Trigger the animation
Ping = givenDate != validDate;

// Reset the property immediately to reset the animation trigger.
// Because the `Trigger.ExitAction` is deleted, 
// the currently running animation will complete the timeline.
Ping = false;

如果您想更优雅地实现逻辑,您可以切换Ping 属性并为true 状态定义Trigger.EnterAction,为false 状态定义Trigger.ExitAction(从而将每个状态转换为验证错误信号):

public bool Ping

    get => _ping;
    set
    
        if (value && !_ping)
        
            _ping = value;
            OnPropertyChanged();
        
    


private DateTime giveValidDate(string posn, DateTime givenDate)

    DateTime validDate = new DateTime();
    // [...A Load of validation that results in a valid Date output...] //

    // Trigger the animation by toggling the property.
    if (givenDate != validDate)
    
      Ping ^= true;
    

    return validDate;


    <DataTrigger Binding="Binding Ping" Value="true">
        <DataTrigger.EnterActions>
            <BeginStoryboard>
                <Storyboard>
                    <ColorAnimation Storyboard.TargetProperty="Foreground.Color"
                                    From="StaticResource Bad_Bright" To="StaticResource Text_Light" FillBehavior="Stop"
                                    Duration="0:0:0:1.0"/>
                </Storyboard>
            </BeginStoryboard>
        </DataTrigger.EnterActions>
        <DataTrigger.ExitActions>
            <BeginStoryboard>
                <Storyboard>
                    <ColorAnimation Storyboard.TargetProperty="Foreground.Color"
                                    From="StaticResource Bad_Bright" To="StaticResource Text_Light" FillBehavior="Stop"
                                    Duration="0:0:0:1.0"/>
                </Storyboard>
            </BeginStoryboard>
        </DataTrigger.ExitActions>
    </DataTrigger>

【讨论】:

除了属性设置器中的 OnPropertyChanged(); 添加之外,我特别感谢花时间解释用法和(在这种情况下是问题)我之前为修复问题。我没有意识到即使特定更改不会导致 UI 发生更改,也应该通知属性更改。我还将实施您提出的改进建议。 这与 UI 的变化无关。它是关于更新数据绑定的。为了让Binding 更新其源/目标,属性必须引发通知(通过将属性实现为依赖属性或通过实现 INotifyPropertyChanged),或者调用者必须通过调用例如 BindingExpression.UpdateTarget 来显式更新绑定。您已在 DataTrigger.Binding 属性上配置了一个绑定。现在您必须注意通知此绑定有关数据更改。

以上是关于WPF DataTrigger 动画只触发一次的主要内容,如果未能解决你的问题,请参考以下文章

WPF ItemsControl DataTrigger EnterActions动画未启动

WPF触发器(Trigger) - DataTrigger

WPF之触发器

wpf 触发器理解

Wpf 绑定 + 数据触发器

WPF DataGrid - 如何设置正确的 DataTrigger 绑定到单元格的数据源(而不是行的源)