次の方法で共有


how to work with animations in Silverlight in the mvvm pattern

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’>

 

Why use animations?

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 should call the attention of the users that something has happened and make it very clear what is happening.
  • Animations should never get in the way of the user:
    • Animations should not be distracting from what’s important.
    • Animations should not slow down the user. If the user has to wait until an animation is done, then the animation is hindering.

 

Who is responsible for animations?

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:

  • Logical Event that should trigger the animation:
    Your ViewModel should raise an event to notify a logical state change. This can either be a pure ‘event’, but it’s typically easier create an observable property (by implementing INotifyPropertyChanged on your VM). If the logical event occurs, you can change the value of that property on your ViewModel. This is something you can test in your Unit Test.
  • Animated visualization of the event:
    The View should define the animation, for example by defining a storyboard. This animation can easily be changed by a designer. Then to visualize the event and start the animation, the view should listen to the event or monitor the property for changes. When the property changes, the animation should be triggered.

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.

Triggering animations from your ViewModel

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:

image

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:

Define a bindable property on your ViewModel

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);

Define animation in my view

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>

Trigger the animation when the state changes

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