WPF DataTrigger 动画只触发一次

WPF DataTrigger Animation only fires once

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

在 MainViewModel 中,我有一个 Ping 属性,它在每次设置为“true”时通知 UI,以及一个验证方法,它在每次设置为 Ping = true 时通知 UI覆盖日期:

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 上看到过许多关于同一问题的其他问题,但解决方案始终是在输入操作中添加行 <StopStoryboard BeginStoryboardName="Pinger"/>,将行 <RemoveStoryboard BeginStoryboardName="Pinger"/> 添加到退出操作或将 FillBehavior="Stop" 添加到故事板。我已经在所有我能想到的地方尝试了每一种组合,但问题仍然存在。

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

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

WPF Storyboard only fires once

WPF Fade In / Out only runs once

您必须在重置 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.EnterActionTrigger.ExitAction 对于 false 状态(从而将每个状态转换为验证错误信号):

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>