One of the things I found a bit more challenging than it should have been is working with animations in Silverlight. Often people will resort to code behind and triggering animations from code. However, I don’t really like that approach, because animations are a form of visualization. Visualizations should be created by designers. And I don’t think designers should have to be able to write code. So I’m going to show a pure XAML – databinding based way to trigger animation, by using a very simple attached property.
This post is part of my series on MVVM.
<Back to intro>
<Back to ‘How to apply the ViewModel pattern to new windows’>
Using animations is a double edged sword. On the one hand, when abused, they can create a very flashy, but totally unusable UI’s. For example, moving advertising banners on websites can make that website much less usable. They distract from the actual content and make it harder for users to find. On the other hand, when you use animations properly, it can really enhance the user experience. For example, in Windows, if you minimize a window, it doesn’t just vanish. You see it quickly shrink and move to the position on the taskbar where it was minimized to. That makes it easy to see what happened, and where to find your window when you want to get it back.
I typically use the following rules to judge if an animation is valuable:
Animations can be an important part of your application. Often animations should be triggered by Business Logic. For example, if your application has network connectivity and you detect that the internet connection is lost, you might wish to start an animation to get the attention of the users and notify them of this state change. In this case, you might argue that the logic (your VieowModel) should trigger the animation. However, animations are also a form of visualization. Often, they are closely tied to visual elements (because they will be animated). So that means the animations should live in the View.
If you start your animations in directly from your ViewModel, you are directly interacting with visual elements from your ViewModel. That means you are breaking the separation of concerns between the Visualization and the Logic of your UI. Basically, you are undoing the benefits of the ViewModel pattern.
When it comes to triggering animations from your ViewModel, I try to split the responsibility between the logical event and the visualization of the animation:
You could use the Code-Behind of your views to do this wireup. However, I don’t really like that approach, because there are much simpler ways to do that. I’ll show you how to trigger animations purely from XAML using databinding.
First of all, you have to create a logical property on your ViewModel, that will change when the animation should be triggered.
I’m also demonstrating animations in several places in my sample application:
You can see 4 buttons, that demonstrate different aspects of working with ViewModels. When you click a button, the UI for that particular demo should come into View. However, because some of the UI pieces are used for different parts of the demo, I used animations to make it very clear what UI elements are added and removed when you enter a particular demo.
To enable these animations I followed the following steps:
In my sample, I’ve defined a bindable property on my ViewModel, called “State” and a Command that can update the state. Clicking one of the buttons on my app should change the state and trigger an animation.
1: // Public property that will change
2: public string State
3: {
4: get { return _state; }
5: set
6: {
7: if (_state == value)
8: return;
9: _state = value;
10:
11: OnPropertyChanged("State");
12: }
13: }
14:
15: // In my case, the command that will trigger the state change:
16: public ICommand GoToStateCommand { get; private set; }
17:
18: private void GoToState(string newState)
19: {
20: this.State = newState;
21: }
22:
23: // Create the command object that can trigger the state change
24: GoToStateCommand = new DelegateCommand<string>(GoToState);
The actual animations are defined in the view itself. I decided to use the VisualStateManager for this, but there are other options. Then I’ve created 4 visual states, each corresponding to one of the buttons. Here you can see one example of that:
1: <VisualState x:Name="SimpleViewModel">
2: <Storyboard>
3: <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="mealDetailsView" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[3].(TranslateTransform.X)">
4: <EasingDoubleKeyFrame KeyTime="00:00:00.2000000" Value="592.667"/>
5: </DoubleAnimationUsingKeyFrames>
6: <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="mealDetailsView" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[3].(TranslateTransform.Y)">
7: <EasingDoubleKeyFrame KeyTime="00:00:00.2000000" Value="1022"/>
8: </DoubleAnimationUsingKeyFrames>
9: </Storyboard>
10: </VisualState>
Now that i have a property on my ViewModel that raises an INotifyPropertyChanged.PropertyChanged event and I have a storyboard in place, now I have to tie the two together. In WPF this is fairly simple, because you can use a DataTrigger to trigger the animation. However, in Silverlight, this does not work with ViewModels. So I’ve written a small class that allows you to use databinding to trigger animations. This is just an example of how you could accomplish this.
The VisualStateSetter has a bindable dependency property. When you set it to a certain string value, it will access the Visual State Manager and set the state to that string value. And because it’s a implemented as an attached property, you can apply it to any type of control.
1: public class VisualStateSetter
2: {
3:
4: public static readonly DependencyProperty StateProperty =
5: DependencyProperty.RegisterAttached("State", typeof(string), typeof(VisualStateSetter), new PropertyMetadata(StateChanged));
6:
7: public static void SetState(DependencyObject target, string state)
8: {
9: target.GetValue(StateProperty);
10: }
11:
12: public static string GetState(DependencyObject target)
13: {
14: return target.GetValue(StateProperty) as string;
15: }
16:
17: private static void StateChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
18: {
19: Control host = d as Control;
20:
21: if (host == null)
22: return;
23:
24: VisualStateManager.GoToState(host, e.NewValue as string, true);
25: }
26: }
So this is what using the VisualStateSetter class looks like:
1: <UserControl UI:VisualStateSetter.State="{Binding State}">
2: <!-- The content of the control -->
3: </UserControl>
Here you see me binding the State property of the ViewModel to the Visual States that are defined in my View.
Now it would have been cleaner to have a more logical property on my ViewModel, for example an Enum and implement a converter that converts the Enum values to the Visual States in my View. But that’s not very difficult to do, so I’ll leave that up to you ;)
Hope this helps you to trigger animations in your views from your ViewModels.
_Erwin