15

有没有办法在 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 每个不同的 stateviusal 重复无数次,总是为情节提要提出一个新名称):

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

您可以轻松想象我必须为所有这些数不胜数的 DataTrigger 反复复制和粘贴多少臃肿的 XAML。

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

4

4 回答 4

4

你能试试这样的东西吗?

  • 用不可见的根元素(例如 Border 或 StackPanel)包装所有当前控件模板,其边界框将覆盖整个控件。
  • 为这个包含所有触发器和动画的不可见框创建样式或控件模板。
  • 让动画为不可见框上的任意 Color 属性设置动画。
  • 在所有不同控件的可视化树中,将要设置动画的任何属性绑定到不可见根元素的 Color 属性。
于 2009-11-06T22:38:14.197 回答
1

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

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

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

于 2009-11-10T10:42:57.473 回答
1

我能想到的实现这一目标的最“ XAMLMarkupExtension方式”是创建专用的,它将从资源字典中提取动画并设置必要的属性——我假设这些属性仅限于 和 的子Storyboard.Target集。虽然它需要一些代码隐藏,但它是一次性的,而且,s 被设计为与XAML一起使用。这是最简单的版本:Storyboard.TargetNameStoryboard.TargetPropertyMarkupExtension

[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上述属性的扩展。然而,这是可以实现的,但需要一些额外的努力。Binding支持应该非常简单 - 正确使用XamlSetMarkupExtensionAttribute (加上一些样板代码)的问题。DynamicResource支持会有点棘手,除了 usingXamlSetMarkupExtensionAttribute还需要包装IServiceProvider返回足够的IProvideValueTarget实现,但仍然是可能的。

于 2018-01-26T23:12:37.163 回答
0

我意识到这个问题在发布这篇文章时有点死,但我确实找到了一个只需要很少代码的解决方案。

您可以使用包含您的矩形以及动画和状态触发器的自定义属性(向下滚动到大约图 8)制作一个 UserControl 。此用户控件将定义一个公共属性,例如当更改时会触发颜色更改的状态。

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

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

于 2009-12-18T19:54:35.653 回答