将动画和触发器定义为可重用资源?

时间:2009-10-31 13:16:07

标签: wpf animation resources datatrigger reusability

有没有办法在xaml中某处定义动画(例如作为资源),然后多次重复使用?我在不同的数据模板上有很多独立的画笔,它们独立地需要基于数据触发器来启动相同类型的动画。现在,因为动画似乎必须定义Storyboard.TargetName和Storyboard.TargetProperty。这几乎违背了可重用性的目的。我想以某种方式宣称“使用此动画形成资源,但这次将其应用于另一个元素”。

对我来说,这似乎是一个相当基本,重要和必要的要求,我很惊讶它不是那么直截了当。我在这里错过了什么吗?

同样适用于触发器。假设我有许多不同的视觉元素,它们都使用颜色动画表示相同类型的状态。例如。当“活动”淡入“红色”时,“错误”等淡入绿色。视觉效果之间的唯一区别是它们的形状/视觉树所需的动画行为是相同的,它们在视觉树中的某个地方都有一个元素颜色类型的属性。我认为不难想象一遍又一遍地重新定义相同的动画和数据触发器是多么乏味。每个开发人员都讨厌这个。我拼命寻求一种更简单的解决方案,不需要(或至少很少)c#代码。

到目前为止我想出的是:

在资源中定义动画,这样可以对所有基本状态重复此操作,如激活,激活,非活动,错误):

<ColorAnimationUsingKeyFrames x:Key="deactivatingColorAnimation" 
                    Storyboard.TargetProperty="Material.(MaterialGroup.Children)[0].Brush.(SolidColorBrush.Color)"                    
                    FillBehavior="HoldEnd" RepeatBehavior="Forever" AutoReverse="True">
      <ColorAnimationUsingKeyFrames.KeyFrames>
        <LinearColorKeyFrame KeyTime="00:00:00" Value="Gray"/>
        <LinearColorKeyFrame KeyTime="00:00:0.25" Value="Gray"/>
        <LinearColorKeyFrame KeyTime="00:00:0.5" Value="Gray" />
        <LinearColorKeyFrame KeyTime="00:00:0.75" Value="Gray" />
     </ColorAnimationUsingKeyFrames.KeyFrames>
</ColorAnimationUsingKeyFrames>

在触发器的故事板中使用它(对于每个状态X重复这个数十亿次的每个不同的状态,总是为故事板提出一个新名称):

<DataTrigger Binding="{Binding SubstrateHolder.State}" Value="Deactivating">
        <DataTrigger.EnterActions>
            <BeginStoryboard x:Name="someStateVisualDeactivatingStoryboard">
                <Storyboard Storyboard.TargetName="someStateVisual">
                    <StaticResource ResourceKey="deactivatingColorAnimation" />
                </Storyboard>
            </BeginStoryboard>
        </DataTrigger.EnterActions>
        <DataTrigger.ExitActions>
            <RemoveStoryboard BeginStoryboardName="someStateVisualDeactivatingStoryboard" />
        </DataTrigger.ExitActions>
</DataTrigger>

你可以很容易地想象我必须为所有那些数以万计的DataTriggers重复复制和粘贴多少臃肿的XAML。

定义所有这些触发器并将其应用于不同的状态视觉效果会很酷。在WPF中如何解决这样的问题?有提示吗?

4 个答案:

答案 0 :(得分:3)

你可以试试这样的吗?

  • 使用不可见的根元素包装所有当前控件模板,例如边框或StackPanel,其边界框将覆盖整个控件。
  • 为此隐藏框创建一个样式或控件模板,其中包含所有触发器和动画。
  • 让动画在隐形框上为任意颜色属性设置动画。
  • 在所有不同控件的可视树中,将要设置动画的任何属性绑定到不可见根元素上的Color属性。

答案 1 :(得分:1)

对于这个一般问题,似乎没有任何好的仅限XAML的解决方案。我最终编写了自己的附加属性,定义了给定元素的所有动画行为。像这样:

<DataTemplate>
   <!-- ...  -->
   <Rectangle Fill="Gray">
     <v:AnimationHelper.Animations>
        <v:StandardColorStateAnimation TargetColorProperty="(Rectangle.Fill).(SolidColorBrush.Color)" TargetStateProperty={Binding State} />
     </v:AnimationHelper.Animations>
   </Rectangle>
<DataTemplate>

其余的(创建动画等)在代码隐藏中完成。

答案 2 :(得分:0)

我发现这个问题在发布时已经有点死了,但我确实找到了一个需要很少代码的解决方案。

你可以创建一个UserControl with custom properties(向下滚动到图8),其中包含你的矩形以及动画和状态触发器。此用户控件将定义一个公共属性,例如状态,在更改时会触发颜色更改。

唯一需要的代码隐藏是在代码中创建变量。

用户控件可以一遍又一遍地重复使用,而无需重写XAML故事板或数据触发器。

答案 3 :(得分:0)

最&#34; XAML 方式&#34;为了实现这个目标,我能想到的是创建专用的MarkupExtension,它将从资源字典中提取动画并设置必要的属性 - 我认为这些仅限于Storyboard.Target的一个子集,{{1} }和Storyboard.TargetName。虽然它需要一些代码隐藏,但它是一次性的努力,而且,Storyboard.TargetProperty s被设计为与 XAML 一起使用。这是最简单的版本:

MarkupExtension

用法非常简单:

[MarkupExtensionReturnType(typeof(Timeline))]
public class AnimationResourceExtension : StaticResourceExtension
{
    //This property is for convienience so that we
    //don't have to remember to set x:Shared="False"
    //on all animation resources, but have an option
    //to avoid redundant cloning if it is
    public bool IsShared { get; set; } = true;

    public DependencyObject Target { get; set; }

    public string TargetName { get; set; }

    public PropertyPath TargetProperty { get; set; }

    public override object ProvideValue(IServiceProvider serviceProvider)
    {
        if (base.ProvideValue(serviceProvider) is Timeline animation)
        {
            //If the animation is shared we shall clone it
            //Checking if it is frozen is optional and we can
            //either clone it or throw an exception
            //(or simply proceed knowing one will be thrown anyway)
            if (IsShared || animation.IsFrozen)
                animation = animation.Clone();
            Storyboard.SetTarget(animation, Target);
            Storyboard.SetTargetName(animation, TargetName);
            Storyboard.SetTargetProperty(animation, TargetProperty);
            return animation;
        }
        else
            throw new XamlException("The referenced resource is not an animation");
    }
}

尽可能简单,但此解决方案有其局限性 - 它不支持上述属性的<FrameworkElement.Resources> <DoubleAnimation x:Key="MyAnimation" From="0" To="1" Duration="0:0:1" /> </FrameworkElement.Resources> (...) <Storyboard> <utils:AnimationResource ResourceKey="MyAnimation" TargetName="SomeElement" TargetProperty="Opacity" /> </Storyboard> Binding扩展。然而,这是可以实现的,但需要一些额外的努力。 DynamicResource支持应该非常简单 - 正确使用XamlSetMarkupExtensionAttribute的问题  (加上一些样板代码)。 Binding支持会有点棘手,除了使用DynamicResource之外,需要包装IServiceProvider以返回足够的IProvideValueTarget实施,但仍有可能。

相关问题