简体   繁体   English

为来自两个不同DataTriggers的同一属性设置动画

[英]Animate the same property from two different DataTriggers

I have a template where I want to animate a certain property (say, Opacity ) in response to different changes in the bound model object. 我有一个模板,可以在其中为特定属性(例如Opacity )设置动画,以响应绑定模型对象的不同变化。 Basically said object has two properties Enabled and Broken and depending on their values both could change the opacity. 基本上,该对象具有两个属性EnabledBroken并且根据它们的值,这两个属性都可以更改不透明度。

This works fairly easy with setters: 这对于二传手来说相当容易:

<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>

because if both DataTriggers apply we just end up overwriting the value that's already 0.5 . 因为如果两个DataTriggers适用,我们最终将覆盖已经0.5的值。 However, with animations I haven't yet figured out how to properly do that. 但是,对于动画,我还没有弄清楚如何正确地做到这一点。 My initial approach was just to use 我最初的方法只是使用

<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>

However, now the problem is that once the second trigger went through both storyboards the opacity is fixed to 1 with the first trigger not animating anything anymore. 但是,现在的问题是,一旦第二个触发器通过两个情节提要板,则不透明度固定为1 ,而第一个触发器不再为任何动画设置动画。 As far as I understood this is because the animation still lives on and overrides the value, and the first animation not changing. 据我了解,这是因为动画仍然存在并覆盖该值,并且第一个动画没有更改。 Changing the FillBehavior to Stop obviously solves that problem but then (equally obvious) Opacity reverts to its previous value after the animation. FillBehavior更改为Stop显然可以解决该问题,但随后(同样明显), Opacity在动画之后恢复为其先前的值。

I then tried using a Setter additional to the animation: 然后,我尝试使用动画之外的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>

But then the first time Enabled changes the setter applies and the animation doesn't play. 但是,第一次Enabled更改时,将应用设置器,并且动画不会播放。 It does in subsequent changes, though. 不过,它会在随后的更改中执行。

Another try to get around both triggers animating the same property was to use To for the enter animation and From for the exit animation. 解决这两个使同一属性动画化的触发器的另一种尝试是使用To作为输入动画,而From作为退出动画。 Which seems to work. 这似乎可行。 But if eg Enabled changes quickly enough, the enter animation is replaced by the exit animation which starts from opacity 0.5 , thus changing the opacity abruptly to 0.5 before animating back to whatever value it should animate to. 但是,如果如Enabled变化的速度不够快,在输入动画是由从不透明开始退出动画取代0.5 ,因此突然改变不透明度为0.5动画回任何值应该动画之前。

Somehow all options I've tried now either don't work or have little details that go wrong and I wasn't able to find good guidance on how to generally approach animating things in response to model changes, especially if those things also need to be animated in the other direction as well. 某种程度上,我现在尝试的所有选项要么不起作用,要么几乎没有出错的小细节,而且我无法找到有关如何总体上对模型进行动画处理以响应模型更改的良好指导,尤其是在那些事情也需要也可以朝另一个方向动画。 Or, like in my case, even done so from two different property changes. 或者,就像我的情况一样,甚至是通过两个不同的属性更改来完成的。

You may need to remove the storyboard applied via other data trigger so that the values are not locked by animations. 您可能需要删除通过其他数据触发器应用的情节提要,以便这些值不会被动画锁定。

based on your input I attempted a sample for you 根据您的输入,我尝试为您提供样本

<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>

in above example you can see that I've used RemoveStoryboard action to remove storyboards applied by other trigger. 在上面的示例中,您可以看到我使用RemoveStoryboard操作来删除其他触发器应用的情节RemoveStoryboard

if the concern is just to smooth out the animation values, you can try HandoffBehavior="Compose" for BeginStoryboard 如果关注的是刚刚理顺动画值,你可以尝试HandoffBehavior="Compose"BeginStoryboard

but seems like you've a little complex scenario you may perhaps create an attached behavior to animate the same based on your needs 但是似乎您的场景有些复杂,您可能会根据自己的需要创建一个附加的行为来对它进行动画处理


Alternate approach 替代方法

I did try to solve the issue by another approach 我确实尝试通过另一种方法解决该问题

xaml 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>

CustomAnimation class CustomAnimation类

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));
    }
}

try the sample above and see if this is what you expect 尝试上面的示例,看看这是否是您期望的

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM