【问题标题】:Why is this WPF animation only triggered once?为什么这个 WPF 动画只触发一次?
【发布时间】:2021-08-14 04:34:00
【问题描述】:

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

<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的值时,边框应该再次淡入淡出。

我没有定义&lt;DataTrigger.ExitActions&gt;,因为MyState很快又会被改变;当MyState 设置为不同的值时,什么都不会发生(动画结束除外)。

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

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

WPF Animation Only Firing Once

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

Animation inside DataTrigger won't run a second time

DataTrigger and Storyboard only getting executed once, related to order of declaration?

我错过了什么?

编辑

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

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

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

编辑:

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

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>

【问题讨论】:

  • 这令人沮丧:为什么这篇文章被否决了?它有代码,一个明确的问题,显示了研究工作......我怎样才能防止这种情况再次发生?如果它被标记为可能重复 - 好吧,也许,但是否决票?
  • 我知道你的感受。但请记住,说你酷的人更多。
  • 什么时候不起作用,即如何重现您的问题?
  • 很可能是因为您没有取消已启动的动画并且它继续保持该属性的值。要准确理解您的问题,请显示足以运行和识别问题的最少可重现代码。

标签: wpf xaml animation


【解决方案1】:

事实证明,问题与动画无关,而是与DataTrigger 或 WPF 的一般节流有关,请参阅Is there a workaround for throttled WPF DataTrigger events?

如上面链接中的示例代码所示,观察到的行为(尤其是关于“仅第一个”和其他实例的行为)并不准确,或者不像看起来那样严格可重现。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-08-10
    • 2021-11-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-07
    • 1970-01-01
    • 2014-08-20
    相关资源
    最近更新 更多