为什么这个 WPF 动画只触发一次? [英] Why is this WPF animation only triggered once?

查看:33
本文介绍了为什么这个 WPF 动画只触发一次?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个具有以下边框定义的自定义控件:

I have a custom control with the following border definition:

<Border Width="10" Height="10"
        CornerRadius="5" 
        Background="Red"
        BorderBrush="White"
        BorderThickness="1">
    <Border.Style>
        <Style TargetType="Border">
            <Setter Property="Opacity" Value="0.0"/>
            <Style.Triggers>
                <DataTrigger Binding="{Binding MyState}" Value="{x:Static my:MyStates.Initializing}">
                    <DataTrigger.EnterActions>
                        <StopStoryboard BeginStoryboardName="Animate"/>
                        <BeginStoryboard x:Name="Animate" HandoffBehavior="SnapshotAndReplace">
                            <Storyboard Duration="0:0:0.4">
                                <DoubleAnimation AutoReverse="True" Storyboard.TargetProperty="Opacity" To="1.0" Duration="0:0:0.2" FillBehavior="Stop" />
                            </Storyboard>
                        </BeginStoryboard>
                    </DataTrigger.EnterActions>
                </DataTrigger>
            </Style.Triggers>
        </Style>
    </Border.Style>
</Border>

意图是:当MyState变成MyStates.Initializing的值时,边框应该再次淡入淡出.

The intention is: when MyState changes to a value of MyStates.Initializing, the border should fade in and out again.

我没有定义,因为MyState会很快再次改变;当 MyState 设置为不同的值时,什么都不应该发生(除了动画完成).

I did not define <DataTrigger.ExitActions>, because MyState will be changed again very quickly; nothing should happen (except the animation finishing) when MyState is set to a different value.

奇怪的是:这只起作用一次.如果我有两个相同控件的实例:两者都会触发一次,但不会再触发.如果我再添加另一个实例,它也不会触发.

The weird thing is: this only works once. If I have two instances of the same control: both will fire once, but never again. If I then add another instance, it will not fire either.

我搜索了无数链接和建议,例如:

I searched through countless links and suggestions, e.g.:

WPF 动画仅触发一次

https://social.msdn.microsoft.com/Forums/vstudio/en-US/7e074dc8-e5da-4840-8b54-8fcb67b43329/storyboard-run-only-one-time-inside-datatriggers?forum=wpf

DataTrigger 内的动画不会再次运行

DataTrigger 和 Storyboard 只获取执行一次,和声明顺序有关吗?

我错过了什么?

编辑:

澄清一下(在阅读下面的评论后),动画在第一次被触发时起作用,但之后就再也没有了.如果 MyState 更改为 Initializing,则它会(显然)正确触发.然后(在动画完成之前,几毫秒或更短的时间内)MyState 更改为 Whatever 并且动画按需要完成.如果 MyState 然后再次更改为 Initializing(稍后,在动画完成后很久),则什么也不会发生.

To clarify (after reading a comment below), the animation works for the first time it is triggered, but then never again. If MyState changes to Initializing, it is triggered (apparently) correctly. Then (before the animation is finished, a few milliseconds or less later) MyState changes to Whatever and the animation finishes as desired. If MyState then is again changed to Initializing (later, long after the animation has finished), nothing happens.

另外:如果我有两个实例响应它们各自的 MyState 更改为 Initializing(在几毫秒或更短的时间内),两者被正确触发.然后我可以添加一个全新的实例,并且不会被触发.

Also: If I have two instances that respond the their respective MyState changing to Initializing (within a few milliseconds or less), both are triggered correctly. I can then add a completely new instance and that will not be triggered.

我还检查的是,如果第二个实例会被正确触发,如果触发器(设置 MyState == Initialized) 出现在第一个实例的动画之后已完成.是的,触发器第一次触发时存在的每个实例都会被正确触发一次.之后:什么都没有...

What I also checked is, if a second instance would be triggered correctly, if the trigger (setting MyState == Initialized) came after the animation of the first instance has finished. Yes, every instance existing when the trigger first fires will be triggered correctly once. After that: nothing...

以下是我为隔离和测试问题而编写的代码.在之前的编辑中,我假设代码没问题,我的错误必须隐藏在其他地方,但我已经测试了 1000 毫秒的睡眠持续时间(见下面的代码).如果我将其减少到 10 毫秒,我的问题仍然存在.因此,这似乎是数据绑定问题 - 而不是动画问题.

Below is the code I wrote to isolate and test the issue. In a previous edit I had assumed that the code was ok and my error must be hidden somewhere else, but I had tested with a sleep duration (see code below) of 1000ms. If I reduce that to 10ms, my problem persists. So, it appears to be a data binding issue - and not an animation issue.

AnimationTestControl.xaml.cs:

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace AnimationTest
{
    public enum MyStates
    {
        None = 0,
        Initializing = 1,
        Ready = 2,
    }

    public class TestItem : INotifyPropertyChanged
    {
        private MyStates _myState;
        public MyStates MyState
        {
            get => _myState;
            set { _myState = value; OnPropertyChanged(); }
        }
        public event PropertyChangedEventHandler PropertyChanged;

        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    public partial class AnimationTestControl : UserControl
    {

        public ObservableCollection<TestItem> TestItems { get; } = new ObservableCollection<TestItem>();
        public AnimationTestControl()
        {
            InitializeComponent();

            TestItems.Add(new TestItem());
        }

        private void ButtonStart_Click(object sender, RoutedEventArgs e)
        {
            Task.Run(() =>
            {
                foreach (var testItem in TestItems)
                {
                    testItem.MyState = MyStates.Initializing;
                    Thread.Sleep(10); //1000ms = good, 10ms = bad
                    testItem.MyState = MyStates.Ready;
                }
            });
        }

        private void ButtonAdd_Click(object sender, RoutedEventArgs e)
        {
            TestItems.Add(new TestItem());
        }
    }
}

AnimationTestControl.xaml:

<UserControl x:Class="AnimationTest.AnimationTestControl"
             x:Name="self"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:AnimationTest"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800">
    <Grid DataContext="{Binding ElementName=self}">
        <StackPanel Orientation="Vertical">
            <ItemsControl ItemsSource="{Binding TestItems}">
                <ItemsControl.ItemTemplate>
                    <DataTemplate>
                        <StackPanel Orientation="Horizontal">
                            <Border Width="10" Height="10"
                                CornerRadius="5" 
                                Background="Red"
                                BorderBrush="White"
                                BorderThickness="1">
                                <Border.Style>
                                    <Style TargetType="Border">
                                        <Setter Property="Opacity" Value="0.0"/>
                                        <Style.Triggers>
                                            <DataTrigger Binding="{Binding MyState}" Value="{x:Static local:MyStates.Initializing}">
                                                <DataTrigger.EnterActions>
                                                    <StopStoryboard BeginStoryboardName="Animate"/>
                                                    <BeginStoryboard x:Name="Animate" HandoffBehavior="SnapshotAndReplace">
                                                        <Storyboard Duration="0:0:0.4">
                                                            <DoubleAnimation AutoReverse="True" Storyboard.TargetProperty="Opacity" To="1.0" Duration="0:0:0.2" FillBehavior="Stop" />
                                                        </Storyboard>
                                                    </BeginStoryboard>
                                                </DataTrigger.EnterActions>
                                            </DataTrigger>
                                        </Style.Triggers>
                                    </Style>
                                </Border.Style>
                            </Border>
                            <TextBlock Text="{Binding MyState}"/>
                        </StackPanel>
                    </DataTemplate>
                </ItemsControl.ItemTemplate>
            </ItemsControl>

            <StackPanel Orientation="Horizontal">
                <Button Content="Start" Click="ButtonStart_Click" />
                <Button Content="Add" Click="ButtonAdd_Click" />
            </StackPanel>
        </StackPanel>
    </Grid>
</UserControl>

推荐答案

原来问题与动画无关,而是与DataTrigger或WPF的一般节流有关,见是否有针对受限制的 WPF DataTrigger 事件的解决方法?.

It turns out that the problem has nothing to do with the animation, but instead with the DataTrigger or WPF's throttling in general, see Is there a workaround for throttled WPF DataTrigger events?.

如上面链接中的示例代码所示,观察到的行为(特别是关于仅优先"和其他实例)并不准确,或者不像看起来那样严格可重现.

As the example code in the link above shows, the observed behavior (esp. regarding "first only" and additional instances) was not accurate or not as strictly reproducible as it seemed.

这篇关于为什么这个 WPF 动画只触发一次?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆