我有一个包含一些按钮的导航栏。当用户单击其中一个时,会触发相同的故事板,但会调用另一个事件处理程序。以下是每个按钮的代码:
<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>
有很多按钮,这是非常重复。有没有办法以更优雅的方式编写它?
答案 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>
资源,并适当地初始化每个按钮的附加属性。