WPF:如何使用相同的动画和“完成”的不同事件处理程序有效地编写多个控件的故事板?

时间:2016-04-03 14:06:37

标签: c# wpf xaml triggers storyboard

我有一个包含一些按钮的导航栏。当用户单击其中一个时,会触发相同的故事板,但会调用另一个事件处理程序。以下是每个按钮的代码:

<Button x:Name="<!-- Button's name -->">
    <Button.Triggers>
        <EventTrigger RoutedEvent="Button.PreviewMouseDown">
            <BeginStoryboard>
                <Storyboard>
                    <DoubleAnimation
                    Storyboard.TargetName="FrameTest"
                    Storyboard.TargetProperty="Opacity"
                    To="0"
                    Duration="0:0:1" 
                    Completed="<!-- Different event handler -->" />
                </Storyboard>
            </BeginStoryboard>
        </EventTrigger>
    </Button.Triggers>
    Messages
</Button>

有很多按钮,这是非常重复。有没有办法以更优雅的方式编写它?

1 个答案:

答案 0 :(得分:1)

如果没有一个好的Minimal, Complete, and Verifiable example能够清楚地显示出您想要实现的目标,那么无法确定什么最适合您。但是,如果我正确理解您的问题,您希望能够创建Storyboard和/或一个或多个Animation对象的单个声明,在多个Button对象上重用该对象,但仍然可以为Completed事件进行不同的事件处理。

不幸的是,Completed事件没有被路由,所以据我所知,你必须直接订阅它。但这至少为我认为应该起作用的几种不同方法留下了空间。

恕我直言,最简单的是实际上只有一个事件处理程序,但它能够处理任何使用目标的完成。请注意,您提供的示例中的Completed事件的发件人是时间轴的AnimationClock。在代码隐藏中,您可以从中检索时间轴(例如,您的DoubleAnimation对象),然后可以通过Storyboard的静态方法从动画中检索目标信息。例如:

string targetName = Storyboard.GetTargetName(((AnimationClock)sender).Timeline);

在上面的示例中,targetName变量将获得值"FrameTest"。通过这种方式,单个事件处理程序可以根据实际动画对象进行适当的响应。

请注意,如果您要将Storyboard对象声明为资源,然后在多个元素上重复使用,那么您希望在其声明中包含x:Shared="false",以确保每个使用它的元素获取自己的Storyboard对象副本。


WPF中常见的另一种技术,通常用于扩展对象的行为方式,附加属性,以及&#34;行为的相关概念。 (表达式扩展支持的东西,但如果你愿意,你也可以自己实现)。与Storyboard.TargetName附加属性一样,这些允许您维护WPF对象的自定义信息和操作。

例如,您可以创建一个附加属性,允许您定义要执行的ICommand对象,要调用的Action委托,或者{{1}时要引发的路由事件事件火灾。在设置属性时,您附加的属性将根据您的实现执行适当的配置。

这样的优势在于您可以使用XAML自定义每个目标元素(即Completed对象)的行为,而不是在共享事件处理程序中硬编码信息。


附录:

根据您的评论和对您发布的其他问题的引用,您似乎还有以下附加条件:

  • 只有一个对象被动画化,而你有多个按钮都会影响这个对象。
  • 该对象是Button对象,您希望能够在动画完成时更新此对象的Frame属性。
  • 您希望能够声明Content对象一次,并且能够让每个按钮启动动画,然后单独定义动画完成时应该发生的事情。

    第三点是有问题的,因为Storyboard及其动画甚至不知道启动它们的Storyboard对象,因此动画的Button事件处理程序会改变它如果没有额外的努力,基于启动它的Completed实例的行为是不可能的。

基于这种理解,下面是一个简单的代码示例,它显示一种可能的方式可以实现上述目标。

(旁白:我强调&#34;一种可能的方式&#34;,因为WPF的巨大灵活性的一个后果是,通常会有大量不同的方法来完成同样的目标。我们这些不是WPF专家的人发现我们可能会或者可能不知道最好的做某事的方式,因为我们找到了一些方式来做并停在那里。:))

总之...

首先,整个示例依赖于自定义附加属性,如此处所示(这可以使用&#34;行为&#34;成语也可以 - 一种特殊类型的附加属性 - 但恕我直言,这会不必要地使例如,即使这是一个很好的方法):

Button

接下来,我们需要在代码隐藏中声明一些事件处理程序。在这种情况下,每个按钮一个,但当然您可以通过向附加属性添加更多上下文来进一步概括,这将允许事件处理程序知道源。

注意:因为上下文允许单个事件处理程序处理每个按钮的动画完成,所以每个按钮获取都很重要它是class AttachStoryboard { public RoutedEvent Trigger { get; set; } public Storyboard Storyboard { get; set; } public event EventHandler Completed; public void RaiseCompleted() { EventHandler handler = Completed; if (handler != null) { handler(this, EventArgs.Empty); } } } static class StoryboardHelper { public static readonly DependencyProperty AttachStoryboardProperty = DependencyProperty.RegisterAttached( "AttachStoryboard", typeof(AttachStoryboard), typeof(StoryboardHelper), new PropertyMetadata(_OnAttachStoryboardChanged)); private static void _OnAttachStoryboardChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { FrameworkElement target = (FrameworkElement)d; AttachStoryboard attachStoryboard = (AttachStoryboard)e.NewValue; if (attachStoryboard != null) { BeginStoryboard beginStoryboard = new BeginStoryboard { Storyboard = attachStoryboard.Storyboard }; EventTrigger trigger = new EventTrigger(attachStoryboard.Trigger); trigger.Actions.Add(beginStoryboard); attachStoryboard.Storyboard.Completed += (sender, e1) => attachStoryboard.RaiseCompleted(); target.Triggers.Add(trigger); } } public static void SetAttachStoryboard(FrameworkElement target, AttachStoryboard value) { target.SetValue(AttachStoryboardProperty, value); } public static AttachStoryboard GetAttachStoryboard(FrameworkElement target) { return (AttachStoryboard)target.GetValue(AttachStoryboardProperty); } } 的私有副本,使用Storyboard(参见下面的XAML)。因此,进一步概括这一点的一个动机是,如果你这样做,你可以使用单个x:Shared="false"资源对象,而不必设置Storyboard

示例的整个x:Shared="false"

MainWindow.cs

对于每个按钮,它会更新public partial class MainWindow : Window { private readonly Storyboard storyboard2; public MainWindow() { InitializeComponent(); storyboard2 = (Storyboard)FindResource("storyboard2"); } private void button1_Completed(object sender, EventArgs e) { frame1.Content = "Button #1 Content"; storyboard2.Begin(frame1); } private void button2_Completed(object sender, EventArgs e) { frame1.Content = "Button #2 Content"; storyboard2.Begin(frame1); } private void button3_Completed(object sender, EventArgs e) { frame1.Content = "Button #3 Content"; storyboard2.Begin(frame1); } } 对象的Content属性,然后启动&#34;淡入&#34;动画以显示更新的内容。

最后,还有XAML:

Frame

以上简单地声明了正在使用的两个<Window x:Class="TestSO36386403SharedStoryboard.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:l="clr-namespace:TestSO36386403SharedStoryboard" Title="MainWindow" Height="350" Width="525"> <Window.Resources> <Style TargetType="Button"> <Setter Property="HorizontalAlignment" Value="Left"/> <Setter Property="Margin" Value="5"/> </Style> <Storyboard x:Key="storyboard1" x:Shared="false"> <DoubleAnimation Storyboard.TargetName="frame1" Storyboard.TargetProperty="Opacity" To="0" Duration="0:0:1" FillBehavior="Stop"/> </Storyboard> <Storyboard x:Key="storyboard2"> <DoubleAnimation Storyboard.TargetName="frame1" Storyboard.TargetProperty="Opacity" To="1" Duration="0:0:1" FillBehavior="Stop"/> </Storyboard> </Window.Resources> <StackPanel> <Button x:Name="button1" Content="Button #1"> <l:StoryboardHelper.AttachStoryboard> <l:AttachStoryboard Trigger="Button.PreviewMouseDown" Storyboard="{StaticResource storyboard1}" Completed="button1_Completed"/> </l:StoryboardHelper.AttachStoryboard> </Button> <Button x:Name="button2" Content="Button #2"> <l:StoryboardHelper.AttachStoryboard> <l:AttachStoryboard Trigger="Button.PreviewMouseDown" Storyboard="{StaticResource storyboard1}" Completed="button2_Completed"/> </l:StoryboardHelper.AttachStoryboard> </Button> <Button x:Name="button3" Content="Button #3"> <l:StoryboardHelper.AttachStoryboard> <l:AttachStoryboard Trigger="Button.PreviewMouseDown" Storyboard="{StaticResource storyboard1}" Completed="button3_Completed"/> </l:StoryboardHelper.AttachStoryboard> </Button> <Frame x:Name="frame1" Content="Initial Content"/> </StackPanel> </Window> 资源,并适当地初始化每个按钮的附加属性。