Using Attached Events to Trigger Animations in WPF
This is a pattern I applied when implementing the WPF NotifyIcon component in order to provide animation support for popups, tooltips, and balloon messages. The problem I had to solve was the loose coupling between the NotifyIcon and displayed controls:
Accordingly, I didn’t know anything about these controls at runtime. Nonetheless, I wanted to provide a communication channel to inform that UIElement that it is being displayed. And I wanted to do it declaratively.
Attached Events to the Rescue
Enter attached events. Just like the better known attached properties, they can be declared in a static class and attached to arbitrary dependency objects. Accordingly, a control X does not need to declare an event itself in order to raise it.
If you are working with Expression Blend, chances are high that you are already using attached events quite often. As an example, the Mouse.MouseDown attached event that lets you trigger an animation if the user clicks on an arbitrary control. And nothing stops you from defining your own custom events 🙂
Creating a Sample Application
Let’s create a simple sample. The scenario is the following:
- Sometimes, some kind of critical event occurs (simulated through a button click).
- Every time this happens, we want a “status control” to show an alarm.
We will implement this status control purely in XAML – an attached event will trigger an animation that displays a warning sign:
(For a real-life usage scenario, have a look at the sample application that comes with the WPF NotifyIcon control.)
Preparing the Status Control
The control below is pretty simple. It just contains two images:
- The regular image shows a star. Its initial opacity is set to 100%.
- The second image is the alarm icon. Its initial opacity is set to 0% so it’s not visible (I changed that a little for the screenshot).
Defining the Attached Routed Event
In order to work with a custom event, it needs to be declared first. In the NotifyIcon library, the NotifyIcon control itself exposes the event. In this sample, however, we will declare an Alarm event in its own class called Monitor. Note that everything is static:
/// <summary>/// Hosts the <see cref="AlarmEvent"/> attached routed event. /// </summary> public static class Monitor{ /// <summary> /// Alarm Attached Routed Event /// </summary> public static readonly RoutedEvent AlarmEvent = EventManager.RegisterRoutedEvent("Alarm", RoutingStrategy.Bubble, typeof (RoutedEventHandler), typeof (Monitor)); }
BTW: I didn’t write the code above on my own – code snippets greatly simplify things here. I’ve used one of Dr. WPF’s snippets which will not only generate the event declarations but also some helper methods to deal with events in code.
Raising the Event
The screenshot below shows a simple form that contains a button and the StatusRenderer user control. In order to keep things simple, we will simply raise the attached routed event on the status control every time the user clicks on the button.
In order to trigger the event, we’ll add a simple static helper method to the Monitor class:
/// <summary> /// A static helper method to raise the Alarm event on a target element. /// </summary> /// <param name="target">UIElement or ContentElement on which to raise /// the event</param> internal static RoutedEventArgs RaiseAlarmEvent(DependencyObject target) { if (target == null) return null; RoutedEventArgs args = new RoutedEventArgs(); args.RoutedEvent = AlarmEvent; if (target is UIElement) { (target as UIElement).RaiseEvent(args); } else if (target is ContentElement) { (target as ContentElement).RaiseEvent(args); } return args; }
We want to raise the event every time the button on the main form is clicked. Accordingly, we invoke RaiseAlarmEvent in the button’s click event handler.
private void btnTrigger_Click(object sender, RoutedEventArgs e) { //raise the attached event on the status control Monitor.RaiseAlarmEvent(status); }
Consuming the Event
The nice thing is – once the event is in place, it can be consumed through XAML only, so there’ no need to write code. Lets complete the sample by adding a simple listener to our user control that triggers an animation. I’m using Blend for that job.
1: Create a new event trigger
Start by clicking on the + Event tab in Blend. You will see that the Monitor.Alarm event is not available through this list. Accordingly, we will just use the Loaded event that is offered for now…
2. Create a storyboard to animate the UI
Next, click on the + button to create a new storyboard. For the sample application, I created a storyboard called ShowWarning that fades in the invisible warning sign and hides the default image.
3: Switch to the Monitor.Alarm event
In the first step, we registered our animation to be triggered whenever the user control’s Loaded event fires. However, we want our animation to be fired upon the attached Monitor.Alarm event. As Blend does not provide attached events through its UI, this needs to be changed XAML. Switch to XAML view and find the following line part.
<UserControl.Triggers> <EventTrigger RoutedEvent="FrameworkElement.Loaded"> <BeginStoryboard Storyboard="{StaticResource ShowWarning}"/> </EventTrigger> </UserControl.Triggers>
Now change the RoutedEvent attribute in order to look like this:
<UserControl.Triggers> <EventTrigger RoutedEvent="att:Monitor.Alarm"> <BeginStoryboard Storyboard="{StaticResource ShowWarning}"/> </EventTrigger> </UserControl.Triggers>
…and of course, you also need to register the namespace prefix in the header of user control:
<UserControl xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:att="clr-namespace:AttachedEventSample" ... >
The Result
That’s it. Now, as soon as you click the button, the attached event is being consumed by the user control and triggers the animation. Without a single line of code on the consumer side 🙂
Great sample. I like it – I like it a lot, especially the way you’re demonstrating Blend.
Thanks, Pete – I’m glad you like it! 🙂
oh, my, god. 500+ lines of code in WPF to say
myStoryboard.Play();
in Silverlight. There has got to be an eiser way to trigger an animation.
I downloaded your code, but is so split up i can’t tell how to merge all that into an existing WPF.
How about a button that calls a storyboard with code that lives all on one page?
seems soooooo verbose.
ok this is what you need:
using System.Windows.Media.Animation;
{ Storyboard storyB = (Storyboard)FindResource(“mtStoryB”);
BeginStoryboard(storyB);
}
Great article, thanks very much.
I am writting a MVVM application where the XAML “View” is decoupled from the C# “ViewModel” (Not codebehind). There can be several instances of the same page each DataBound to thier own instance of the “ViewModel”, thus several instance of the same ViewModel class will exist – so it can not be static.
I wish to trigger a Story Board (in the “View”) from code in “ViewModel”. But trying to use your approach above I get stuck at the equivilent of this line:
Monitor.RaiseAlarmEvent(status)
This works for you because it is in Code Behind and you have a reference to the “status” Element:
For me I am trying to raise the event from the seperate “ViewModel” class with no reference to such Elements in the “View” (and I am not sure what element I would choose anyway).
Can you suggest a way I can programatically trigger a story board from my “ViewModel” Class ??
THanks Philipp,
Terry Clancy (Microsoft)
Terry,
I don’t know the specifics of your project, obviously, but isn’t submitting the View to the VM through a command an option?
With regards to my sample: I could bind a command to the button (rather than using the event in code-behind), and submit the control as a command parameter. Would that work for you?
Cheers,
Philipp
Thanks Philipp for your VERY rapid response.
BTW I am beginning with WPF so please be patient 🙂
Please see my responses and questions after >>>>> below <<<<>>>> I am actually not sure what you mean by “submitting the View to the VM through a command” <<<<>>>> I do databind to Fields in the ViewModel. I also defined ICommands in my ViewModel which I data bind the Command attribute of various WPF element to. This allows me to connect from the XAML View “TO” the C# View model to access and display data and execute commands. It does not seem so easy to initiate the communication in the reverse direction from the ViewModel back to the View. In my case I wish to add code to a timer tick event handler in the ViewModel so that each time the timer ticks the Animation is started. <<<<>>>> Thanks but no that would not work – I am wanting a timer tick event handler in the ViewModel to initiate the Animation rather than a button. <<<<>>>> Thanks again Philipp Terry
Sorry – Lost your text in that last post – but I think you get my meaning 🙂
So Good!
I am Chinese…
Through your blog,I learn more about WPF,thank you
Thanks for your kind words Mengxin – happy coding.
Great example – thanks. It works for me, however when I select the Alarm trigger in Blend, it immediately disappears from the list (I am using Blend v4). I’m guessing this is a bug with Blend? I have a custom button (User control) that “clicks” when a user hovers using Kinect – I want the designers to be able to hook up animations without having to call me to do some c# every 5 minutes. Any suggestions?
Year 2012. How come Blend 4 still doesn’t show that attached event? I tried to move it to another assembly – still no luck… Is there some attribute to set, or anything?
Have you ever considered writing an e-book or guest authoring on other websites?
I have a blog centered on the same subjects you discuss and would really like
to have you share some stories/information. I know my visitors would
value your work. If you’re even remotely interested, feel free to shoot me an e mail.