在 XAML 中执行命令时触发动画

Trigger animation when Command is Executed in XAML

我有一个视图模型,它定义了一个 RelayCommand,多个控件将其作为它们的 Command 绑定。我想在执行(或完成执行)时在绑定到此命令的所有控件上触发动画。该命令可以由 UI 控件执行,也可以在模型事件的视图模型中执行。

举个例子 假设我想要一个 Button 在执行 MyCommand 绑定时闪烁金币,无论是通过按钮点击还是其他地方别的。 Hyperlink,也绑定到 MyCommand,最终会使按钮闪烁,尽管我不是在寻找这个特定的解决方案(超链接直接触发按钮闪烁)。这是此示例的 XAML:

<Button Content="My Command"
        Command="{Binding MyCommand}">

    <Button.Background>
        <SolidColorBrush x:Name="buttonBrush"
                         Color="DimGray" />
    </Button.Background>

    <Button.Resources>
        <ColorAnimationUsingKeyFrames x:Key="flash"
                                      Storyboard.TargetProperty="Color">
            <DiscreteColorKeyFrame Value="Gold"
                                   KeyTime="0:0:0" />
            <DiscreteColorKeyFrame Value="Gold"
                                   KeyTime="0:0:0.3" />
            <LinearColorKeyFrame Value="DimGray"
                                 KeyTime="0:0:0.7" />
        </ColorAnimationUsingKeyFrames>
    </Button.Resources>

    <Button.Triggers>
        <EventTrigger RoutedEvent="Binding.Executed">
            <BeginStoryboard>
                <Storyboard Storyboard.TargetName="buttonBrush"
                            Storyboard.TargetProperty="Color">
                    <StaticResource ResourceKey="goldFlash" />
                </Storyboard>
            </BeginStoryboard>
        </EventTrigger>
    </Button.Triggers>
</Button>

<TextBlock>
    <Hyperlink Command="{Binding MyCommand}">
        My Command...
    </Hyperlink>
</TextBlock>

我编造了 RoutedEvent="Binding.Executed" 来演示我正在尝试做的事情,我知道那个事件不存在。

更新

根据@BionicCode 的建议,我将 ExecutingExecuted 事件添加到我的 RelayCommand class。在我的视图模型中,当我的 UI.

中的按钮执行命令时,我会收到这些事件

然后我向视图模型添加了一个 MyCommandExecuted 事件,该事件在命令的 Executed 事件发生时引发。

在我的 MainWindow 中,我为 MyCommandExecuted 事件添加了一个事件处理程序,并且该事件正常工作。

接下来我在 MainWindow 中创建了一个 RoutedEvent(抱歉,这是 VB.Net),使用的代码来自 How to: Create a Custom Routed Event:

' Create a custom routed event by first registering a RoutedEventID
' This event uses the bubbling routing strategy
Public Shared ReadOnly TapEvent As RoutedEvent = EventManager.RegisterRoutedEvent("Tap", RoutingStrategy.Bubble, GetType(RoutedEventHandler), GetType(MainWindow))

' Provide CLR accessors for the event
Public Custom Event Tap As RoutedEventHandler
    AddHandler(ByVal value As RoutedEventHandler)
        Me.AddHandler(TapEvent, value)
    End AddHandler

    RemoveHandler(ByVal value As RoutedEventHandler)
        Me.RemoveHandler(TapEvent, value)
    End RemoveHandler

    RaiseEvent(ByVal sender As Object, ByVal e As RoutedEventArgs)
        Me.RaiseEvent(e)
    End RaiseEvent
End Event

我在 MyCommandExecuted 事件处理程序中引发此事件:

' This method raises the Tap event
Private Sub RaiseTapEvent()
    Dim newEventArgs As New RoutedEventArgs(MainWindow.TapEvent)
    MyBase.RaiseEvent(newEventArgs)
End Sub

' For demonstration purposes we raise the event when the MyButtonSimple is clicked
Private Sub MyCommandExecuted() Handles _myViewModel.MyCommandExecuted
    Me.RaiseTapEvent()
End Sub

这段代码被执行,所以到这里为止的一切都在工作。最后,在 XAML 中,我为 local:MainWindow.Tap 创建了一个 EventTrigger:

<Border Width="100" Height="100" x:Name="MyBorder" Background="AliceBlue">
    <Border.Triggers>
        <EventTrigger RoutedEvent="local:MainWindow.Tap">
            <BeginStoryboard>
                <Storyboard>
                    <ColorAnimationUsingKeyFrames Storyboard.TargetName="MyBorder"
                                                  Storyboard.TargetProperty="(Border.Background).(SolidColorBrush.Color)"
                                                  FillBehavior="Stop"
                                                  Duration="0:0:2">
                        <DiscreteColorKeyFrame Value="Gold"
                                               KeyTime="0:0:0" />
                        <DiscreteColorKeyFrame Value="DarkOrange"
                                               KeyTime="0:0:1" />
                    </ColorAnimationUsingKeyFrames>
                </Storyboard>
            </BeginStoryboard>
        </EventTrigger>
    </Border.Triggers>
</Border>

这行不通...事件不会 "bubble down"(有道理)但是如果你把 EventTrigger 放在 Window 级别,就像@BionicCode 的例子一样, 有效,问题已解决。

WPF 中的动画非常强大。您可以将故事板定义为一种资源,它将以某种方式更改控件,然后将其作为静态资源从任何 BeginStoryboard.

访问

这应该可以完成您想要的。

<StackPanel>
    <StackPanel.Resources>
        <Storyboard x:Key="flash" 
                    Storyboard.TargetName="Button"
                    Storyboard.TargetProperty="Background.(SolidColorBrush.Color)" 
                    Duration="0:0:0">
            <ColorAnimationUsingKeyFrames>
                <DiscreteColorKeyFrame Value="Gold"
                               KeyTime="0:0:0" />
                <DiscreteColorKeyFrame Value="Gold"
                               KeyTime="0:0:0.3" />
                <LinearColorKeyFrame Value="DimGray"
                             KeyTime="0:0:0.7" />
            </ColorAnimationUsingKeyFrames>
        </Storyboard>
    </StackPanel.Resources>
    <Button Content="My Command" Background="DimGray" x:Name="Button">
        <Button.Triggers>
            <EventTrigger RoutedEvent="Button.Click">
                <BeginStoryboard Storyboard="{StaticResource flash}"/>
            </EventTrigger>
        </Button.Triggers>
    </Button>
    <TextBlock>
        <Hyperlink>
            My Command ...
        </Hyperlink>
        <TextBlock.Triggers>
            <EventTrigger RoutedEvent="Hyperlink.Click">
                <BeginStoryboard Storyboard="{StaticResource flash}"/>
            </EventTrigger>
        </TextBlock.Triggers>
    </TextBlock>
</StackPanel>

您应该在视图模型中实现一个 MyCommandExecuted 事件。正在执行动画的 UI 组件应该监听这个事件,例如通过订阅当前 DataContext 的视图模型。

MyCommandExecuted 的事件处理程序然后最好通过引发路由事件来启动动画,例如 CommandExecuted 将由相应的 EventTrigger.
处理 或者,您可以在处理 MyCommandExecuted 事件时直接在代码隐藏中启动动画。但是在XAML中使用EventTrigger就方便多了。

例子

ViewModel.cs

public class ViewModel : INotifyPropertyChanged
{
  public ICommand MyCommand => new RelayCommand(ExecuteMyCommand, (param) => true);
  public event EventHandler MyCommandExecuted;

  private void ExecuteMyCommand(object obj)
  {
    // TODO::Implement command operation ...

    OnMyCommandExecuted();
  }

  protected virtual void OnMyCommandExecuted()
  {
    this.MyCommandExecuted?.Invoke(this, EventArgs.Empty);
  }
}

MainWindow.xaml.cs

partial class MainWindow : Window
{
  #region Routed Events

  public static readonly RoutedEvent AnimationRequestedRoutedEvent = EventManager.RegisterRoutedEvent(
    "AnimationRequested",
    RoutingStrategy.Bubble,
    typeof(RoutedEventHandler),
    typeof(MainWindow));

  public event RoutedEventHandler AnimationRequested
  {
    add => AddHandler(MainWindow.AnimationRequestedRoutedEvent, value);
    remove => RemoveHandler(MainWindow.AnimationRequestedRoutedEvent, value);
  }

  #endregion Routed Events

  public MainWindow()
  {
    InitializeComponent();

    var viewModel = new ViewModel();
    viewModel.MyCommandExecuted += TriggerAnimation_OnCommandExecuted;

    this.DataContext = viewModel;
  }

  private void TriggerAnimation_OnCommandExecuted(object sender, EventArgs e)
  {
    RaiseEvent(new RoutedEventArgs(MainWindow.AnimationRequestedRoutedEvent, this));
  }
}

MainWindow.xaml

<Window>
  <Button x:Name="AnimatedButton" 
          Content="Execute My Command"
          Command="{Binding MyCommand}" 
          Background="DimGray" />

  <Window.Triggers>
    <EventTrigger RoutedEvent="MainWindow.AnimationRequested">
      <BeginStoryboard>
        <Storyboard>
          <ColorAnimationUsingKeyFrames Storyboard.TargetName="AnimatedButton"
                                        Storyboard.TargetProperty="(Button.Background).(SolidColorBrush.Color)" 
                                        FillBehavior="Stop"
                                        Duration="0:0:0.7">
            <DiscreteColorKeyFrame Value="Gold"
                                   KeyTime="0:0:0" />
            <DiscreteColorKeyFrame Value="DarkOrange"
                                   KeyTime="0:0:0.3" />
          </ColorAnimationUsingKeyFrames>
        </Storyboard>
      </BeginStoryboard>
    </EventTrigger>
  </Window.Triggers>
</Window>