I have a View Model that defines a RelayCommand
which multiple controls have as their Command
binding. I would like to trigger an animation on all the controls that are bound to this command when it executes (or is done executing). The command can be executed by UI controls and in the View Model from a Model event.
Just as an example imagine I want a Button
to flash Gold when the MyCommand
binding is executed, be it from the button click or somewhere else. A Hyperlink
, also bound to MyCommand
, would end up making the button flash, although I am not looking for this specific solution (hyperlink triggers button flash directly). Here is the XAML for this example:
<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>
I made up RoutedEvent="Binding.Executed"
to demonstrate what I am trying to do, I understand that that event doesn't exist.
Following @BionicCode's suggestion I added Executing
and Executed
events to my RelayCommand
class. In my View Model, I receive these events when the command is executed by a button in my UI.
I then added a MyCommandExecuted
event to the View Model that gets raised when the command's Executed
event occurs.
In my MainWindow
, I added an event handler for the MyCommandExecuted
event and this event is working properly.
Next I created a RoutedEvent
in MainWindow
(sorry, this is VB.Net), using code from 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
and I raise this event in the MyCommandExecuted
event handler:
' 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
This code gets executed so everything up to here is working. Finally, in the XAML, I created an EventTrigger
for local:MainWindow.Tap
:
<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>
This doesn't work... events don't "bubble down" (makes sense) but if you put the EventTrigger
at the Window
level like in @BionicCode's example, it works, so problem solved.
You should implement a MyCommandExecuted
event in the view model. The UI component that is executing the animation, should listen to this event e.g. by subscribing to the view model which is the current DataContext
.
The event handler of MyCommandExecuted
then starts the animation preferably by raising a routed event e.g., CommandExecuted
which will be handled by the corresponding EventTrigger
.
Alternatively you could start the animation directly in code-behind when handling the MyCommandExecuted
event. But using EventTrigger
in XAML is much more convenient.
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>