wpf - 从两个不同的 DataTriggers 为同一属性设置动画

标签 wpf xaml animation

我有一个模板,我想在其中对某个属性(例如,Opacity)进行动画处理,以响应绑定(bind)模型对象中的不同更改。基本上,该对象有两个属性 EnabledBroken,并且根据它们的值,两者都可以更改不透明度。

这对于 setter 来说相当容易:

<DataTemplate.Triggers>
    <DataTrigger Binding="{Binding Enabled}" Value="False">
        <Setter TargetName="X" Property="Opacity" Value="0.5"/>
    </DataTrigger>

    <DataTrigger Binding="{Binding Broken}" Value="True">
        <Setter TargetName="X" Property="Opacity" Value="0.5"/>
        <Setter TargetName="Y" Property="Visibility" Value="Visible"/>
    </DataTrigger>
</DataTemplate.Triggers>

因为如果两个DataTriggers都适用,我们最终只会覆盖已经是0.5的值。然而,对于动画,我还没有弄清楚如何正确地做到这一点。我最初的方法只是使用

<DataTemplate.Triggers>
    <DataTrigger Binding="{Binding Enabled}" Value="False">
        <DataTrigger.EnterActions>
            <BeginStoryboard>
                <Storyboard>
                    <DoubleAnimation Storyboard.TargetName="X" Storyboard.TargetProperty="Opacity" To="0.5" Duration="0:0:0.2"/>
                </Storyboard>
            </BeginStoryboard>
        </DataTrigger.EnterActions>
        <DataTrigger.ExitActions>
            <BeginStoryboard>
                <Storyboard>
                    <DoubleAnimation Storyboard.TargetName="X" Storyboard.TargetProperty="Opacity" To="1" Duration="0:0:0.2"/>
                </Storyboard>
            </BeginStoryboard>
        </DataTrigger.ExitActions>
    </DataTrigger>

    <DataTrigger Binding="{Binding Broken}" Value="True">
        <Setter TargetName="Y" Property="Visibility" Value="Visible"/>

        <DataTrigger.EnterActions>
            <BeginStoryboard>
                <Storyboard>
                    <DoubleAnimation Storyboard.TargetName="X" Storyboard.TargetProperty="Opacity" To="0.5" Duration="0:0:0.2"/>
                </Storyboard>
            </BeginStoryboard>
        </DataTrigger.EnterActions>
        <DataTrigger.ExitActions>
            <BeginStoryboard>
                <Storyboard>
                    <DoubleAnimation Storyboard.TargetName="X" Storyboard.TargetProperty="Opacity" To="1" Duration="0:0:0.2"/>
                </Storyboard>
            </BeginStoryboard>
        </DataTrigger.ExitActions>
    </DataTrigger>
</DataTemplate.Triggers>

但是,现在的问题是,一旦第二个触发器穿过两个 Storyboard,不透明度就会固定为 1,而第一个触发器不再为任何内容设置动画。据我了解,这是因为动画仍然存在并覆盖该值,并且第一个动画没有改变。将 FillBehavior 更改为 Stop 显然可以解决该问题,但随后(同样明显)Opacity 在动画结束后恢复为之前的值。

然后我尝试在动画之外使用 Setter:

<DataTrigger Binding="{Binding Enabled}" Value="False">
    <DataTrigger.EnterActions>
        <BeginStoryboard>
            <Storyboard>
                <DoubleAnimation Storyboard.TargetName="X" Storyboard.TargetProperty="Opacity" To="0.5" Duration="0:0:0.2"/>
            </Storyboard>
        </BeginStoryboard>
    </DataTrigger.EnterActions>
    <DataTrigger.ExitActions>
        <BeginStoryboard>
            <Storyboard>
                <DoubleAnimation Storyboard.TargetName="X" Storyboard.TargetProperty="Opacity" To="1" Duration="0:0:0.2"/>
            </Storyboard>
        </BeginStoryboard>
    </DataTrigger.ExitActions>
    <Setter TargetName="X" Property="Opacity" Value="0.5"/>
</DataTrigger>

但是第一次 Enabled 更改时, setter 会应用,并且动画不会播放。不过,在随后的更改中确实如此。

另一种尝试绕过为同一属性设置动画的两个触发器,即使用 To 作为进入动画,使用 From 作为退出动画。这似乎有效。但如果例如Enabled 变化得足够快,进入动画被从不透明度 0.5 开始的退出动画取代,从而在动画之前将不透明度突然更改为 0.5返回到它应该动画化的任何值。

不知何故,我现在尝试过的所有选项要么不起作用,要么有一些小细节出了问题,而且我无法找到关于如何通常处理动画事物以响应模型变化的良好指导,特别是如果这些事情还需要在另一个方向上进行动画处理。或者,就像我的例子一样,甚至通过两个不同的属性更改来完成此操作。

最佳答案

您可能需要删除通过其他数据触发器应用的 Storyboard,以便值不会被动画锁定。

根据您的输入,我尝试为您提供一个示例

<ContentControl>
    <ContentControl.ContentTemplate>
        <DataTemplate>
            <Grid>
                <Ellipse Stretch="Uniform"
                         Fill="Gray"
                         x:Name="Y"
                         Visibility="Collapsed" />
                <Ellipse Stretch="Uniform"
                         Fill="Orange"
                         Margin="8"
                         x:Name="X" />
                <StackPanel HorizontalAlignment="Right">
                    <CheckBox Content="Enabled"
                              x:Name="enabled"
                              IsChecked="True" />
                    <CheckBox Content="Broken"
                              x:Name="broken" />
                </StackPanel>
            </Grid>
            <DataTemplate.Resources>
                <Storyboard x:Key="fadeOut">
                    <DoubleAnimation Storyboard.TargetName="X"
                                     Storyboard.TargetProperty="Opacity"
                                     To="0.5"
                                     Duration="0:0:0.2" />
                </Storyboard>
                <Storyboard x:Key="fadeIn">
                    <DoubleAnimation Storyboard.TargetName="X"
                                     Storyboard.TargetProperty="Opacity"
                                     To="1"
                                     Duration="0:0:0.2" />
                </Storyboard>
            </DataTemplate.Resources>
            <DataTemplate.Triggers>
                <DataTrigger Binding="{Binding IsChecked,ElementName=enabled}"
                             Value="False">
                    <DataTrigger.EnterActions>
                        <RemoveStoryboard BeginStoryboardName="fadeIn2" />
                        <RemoveStoryboard BeginStoryboardName="fadeOut2" />
                        <BeginStoryboard Storyboard="{StaticResource fadeOut}"
                                         x:Name="fadeOut" />
                    </DataTrigger.EnterActions>
                    <DataTrigger.ExitActions>
                        <RemoveStoryboard BeginStoryboardName="fadeIn2" />
                        <RemoveStoryboard BeginStoryboardName="fadeOut2" />
                        <BeginStoryboard Storyboard="{StaticResource fadeIn}"
                                         x:Name="fadeIn" />
                    </DataTrigger.ExitActions>
                </DataTrigger>

                <DataTrigger  Binding="{Binding IsChecked,ElementName=broken}"
                              Value="True">
                    <Setter TargetName="Y"
                            Property="Visibility"
                            Value="Visible" />
                    <DataTrigger.EnterActions>
                        <RemoveStoryboard BeginStoryboardName="fadeIn" />
                        <RemoveStoryboard BeginStoryboardName="fadeOut" />
                        <BeginStoryboard Storyboard="{StaticResource fadeOut}"
                                         x:Name="fadeOut2" />
                    </DataTrigger.EnterActions>
                    <DataTrigger.ExitActions>
                        <RemoveStoryboard BeginStoryboardName="fadeIn" />
                        <RemoveStoryboard BeginStoryboardName="fadeOut" />
                        <BeginStoryboard Storyboard="{StaticResource fadeIn}"
                                         x:Name="fadeIn2" />
                    </DataTrigger.ExitActions>
                </DataTrigger>
            </DataTemplate.Triggers>
        </DataTemplate>
    </ContentControl.ContentTemplate>
</ContentControl>

在上面的示例中,您可以看到我使用了 RemoveStoryboard 操作来删除其他触发器应用的 Storyboard。

如果只是为了平滑动画值,您可以尝试使用 HandoffBehavior="Compose" 来实现 BeginStoryboard

但似乎您有一个有点复杂的场景,您可能可以创建一个附加行为来根据您的需要对其进行动画处理


替代方法

我确实尝试通过另一种方法解决这个问题

xaml

<ContentControl xmlns:l="clr-namespace:CSharpWPF">
    <ContentControl.ContentTemplate>
        <DataTemplate>
            <Grid>
                <Ellipse Stretch="Uniform"
                         Fill="Gray"
                         x:Name="Y"
                         Visibility="{Binding VisibilityY, ElementName=animation}" />
                <Ellipse Stretch="Uniform"
                         Fill="Orange"
                         Margin="8"
                         x:Name="X"
                         Opacity="{Binding OpacityX, ElementName=animation}" />
                <StackPanel HorizontalAlignment="Right">
                    <CheckBox Content="Enabled"
                              x:Name="enabled"
                              IsChecked="True" />
                    <CheckBox Content="Broken"
                              x:Name="broken" />
                    <l:CustomAnimation x:Name="animation"
                                       IsEnabled="{Binding IsChecked,ElementName=enabled}"
                                       IsBroken="{Binding IsChecked,ElementName=broken}" />
                </StackPanel>
            </Grid>
        </DataTemplate>
    </ContentControl.ContentTemplate>
</ContentControl>

自定义动画类

namespace CSharpWPF
{
    public class CustomAnimation : FrameworkElement
    {
        public CustomAnimation()
        {
            IsEnabledProperty.OverrideMetadata(typeof(CustomAnimation), new UIPropertyMetadata(true, (s, e) => AnimateX(s as FrameworkElement, (bool)e.NewValue)));
        }

        static void AnimateX(FrameworkElement elem, bool fadeIn)
        {
            elem.BeginAnimation(OpacityXProperty, new DoubleAnimation(fadeIn ? 1 : 0.5, TimeSpan.FromSeconds(0.2)));
        }

        public bool IsBroken
        {
            get { return (bool)GetValue(IsBrokenProperty); }
            set { SetValue(IsBrokenProperty, value); }
        }

        // Using a DependencyProperty as the backing store for IsBroken.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty IsBrokenProperty =
            DependencyProperty.Register("IsBroken", typeof(bool), typeof(CustomAnimation), new PropertyMetadata(false, (s, e) =>
                {
                    AnimateX(s as FrameworkElement, !(bool)e.NewValue);
                    s.SetValue(VisibilityYProperty, ((bool)e.NewValue) ? Visibility.Visible : Visibility.Collapsed);
                }));

        // Using a DependencyProperty as the backing store for XOpacity.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty OpacityXProperty =
            DependencyProperty.Register("OpacityX", typeof(double), typeof(CustomAnimation), new PropertyMetadata(1.0));

        // Using a DependencyProperty as the backing store for VisibilityY.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty VisibilityYProperty =
            DependencyProperty.Register("VisibilityY", typeof(Visibility), typeof(CustomAnimation), new PropertyMetadata(Visibility.Collapsed));
    }
}

尝试上面的示例,看看这是否是您所期望的

关于wpf - 从两个不同的 DataTriggers 为同一属性设置动画,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25075974/

相关文章:

c# - 如何让随机数/字母生成器生成 3 个字母然后生成 6 个数字?在 WPF 中

wpf - 如何以多窗口模式在应用程序级别启动 RoutedCommand?

xaml - 弹出按钮位于SecondaryCommands 后面

iphone - 如何触发uiview动画再次炒锅

java - 如何将此水平 ViewPager 变压器修改为垂直变压器?

c# - Entity Framework 中应该如何管理一对多关系?

wpf - 在 Canvas 内拉伸(stretch)图像

c# - 在类型 'Template' 中找不到属性 'FrameworkElement'

xaml - 在 Avalonia 中设置窗口图标

html - 将一个 css3 动画替换为另一个