MVVM and animation, revisited

A while back, quite a while back to be honest, I wrote a blog post about using animations in the MVVM pattern. And even if the way of doing it mentioned in the post still works, I would not recommend using it.

Adding the animation support in the way that that blog post says, will couple your VM to the StoryboardManager. Not that this really matters, as it will still be quite testable and so on. But it feels wrong…and I don’t like things that feel wrong…

In this post, I aim to cover a couple of ways that we can trigger animations and state changes based on the VM. And yes, these techniques have been hashed and rehashed on several other blogs, and you might already have read about it, but I still have people come to my blog to read the old post. So apparently it is still an issue for some…

Both ways of handling animations based on the VisualStateManager, but the first one also involves building a custom control. A very simple custom control, but still a custom control. This is just a tad bit on the complicated side, and I will later show how you can achieve more or less the same functionality without a custom control. The reason for the custom control is that it can be reused easily, modified to suit your need and you do not have to add a bunch of kbs to the XAP file as this control is tiny. I know that I am paranoid when it comes to download size, but I guess that comes from having starting doing webdevelopment in the modem days… And adding the Expression assemblies to support Actions and Behaviors do add a bit to the download…

The code for the custom control, called StateControl in this case, is very simple. It does nothing except declare the default style key (as most controls) and a single dependency property. The dependency property is of type string and is called State. It has a corresponding property called State as well as a callback for the changed event.

public class StateControl : Control
{
public static DependencyProperty StateProperty =
DependencyProperty.Register("State", typeof(string), typeof(StateControl), new PropertyMetadata(null, new PropertyChangedCallback(OnStateChanged)));

public StateControl()
{
this.DefaultStyleKey = typeof(StateControl);
}

private static void OnStateChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
((StateControl)sender).OnStateChanged(e);
}
private void OnStateChanged(DependencyPropertyChangedEventArgs e)
{
VisualStateManager.GoToState(this, (string)e.NewValue, true);
}

public string State
{
get { return (string)GetValue(StateProperty); }
set { SetValue(StateProperty, value); }
}
}

The functionality behind this control is, as mentioned before, all about telling the VSM what to do. It simply makes sure that the VSM is told to change to the state with the same name as the State property as soon as it is changed. This property in turn can then be bound to a VM. So when the VM changes its “state”, the control will make sure that it animates to the correct state using the VSM.

There is not a whole lot of code behind the control, but there is even less to be honest in the style for it. The style looks like this

<Style TargetType="local:StateControl">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:StateControl">
<Border />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>

The reason for this ridiculously simple style, is that the style is not supposed to be used. This control is supposed to be restyled every time it is used. As a part of this restyling, we are supposed to add a VSM with the states required for the current situation. Something like this

<ctrl:StateControl State="{Binding State}">
<ctrl:StateControl.Template>
<ControlTemplate>
<Grid>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="StatesGroup">
<VisualStateGroup.Transitions>
<VisualTransition GeneratedDuration="0:0:1"/>
</VisualStateGroup.Transitions>
<VisualState x:Name="Normal" />
<VisualState x:Name="State1">
<Storyboard>
<ColorAnimation To="Blue" Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)" Storyboard.TargetName="rectangle" d:IsOptimized="True"/>
</Storyboard>
</VisualState>
<VisualState x:Name="State2">
<Storyboard>
<ColorAnimation To="Red" Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)" Storyboard.TargetName="rectangle" d:IsOptimized="True" Duration="0"/>
</Storyboard>
</VisualState>
<VisualState x:Name="State3">
<Storyboard>
<ColorAnimation To="#FFFFDE00" Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)" Storyboard.TargetName="rectangle" d:IsOptimized="True" Duration="0"/>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Rectangle x:Name="rectangle" Fill="#FFF4F4F5" HorizontalAlignment="Center" Height="100" Stroke="Black" VerticalAlignment="Top" Width="100"/>

</Grid>
</ControlTemplate>
</ctrl:StateControl.Template>
</ctrl:StateControl>

This style adds 3 different states that can be used (State1, State2 State3). There is also a binding that binds the State property to a State property on the current VM. The current VM implementation looks like this

public class StateControlViewModel : ViewModelBase
{
public StateControlViewModel()
{
SwitchState = new DelegateCommand<string>((state) => {
State = state;
OnPropertyChanged(() => State);
});
}

public string State
{
get;
private set;
}
public ICommand SwitchState
{
get;
private set;
}
}

Very simple. As soon as the ICommand called SwitchState is called, the State property is changed, and the StateControl will make sure that the VSM changes the color of the rectangle over the course of 1 second…

That’s it! It is not complicated, but offers some pretty good things. Especially if we start modifying it to our needs. One can for example switch the VSM that is used for one that can offer notification when a state change is done. Or we could use this as the base for a more specific implementation that suits what we need in a certain scenario…

But the problem with the above solution is that it is an extra control. It is more to maintain. And some might argue that it is giving the VM some responsibility it shouldn’t really have. I am not 100% sure where I stand on that statement though. I understand where it is coming from. But I also feel that the VM is there to make sure the view behaves as it should. But at the same time, is transitions and animations really the responsibility of the VM? And then again, the VM is just changing a property. It doesn’t really know that it is causing an animation… anyhow… There are sometimes reasons for not using this technique…

So how can we do it differently? Well, we can use Actions and Triggers. Silverlight doesn’t have the same Trigger support that WPF has out of the box, but with the introduction of Behaviors and Actions, we did get some of it.

I have for a very long time stayed away from Behaviors and Actions. I don’t know quite why. I think it has to do with the fact that they are in the “wrong” namespace, don’t ask me why…probably part of all of my OCD induced behavior... They are a part of the Expression namespace, and actually comes “out of” Expression Blend. But after having had a little play with them on a project, I realized that they do have quite a lot to offer…

I am not going to go into Behaviors in this post, as they are more of a complete solution to a specific problem. What I want, is more of a loose and open approach to do things, which Actions and Triggers solves nicely.

If you open up Blend and look at the Assets pane, there is a whole section of assets called “Behaviors”. This section contains both Behaviors and Actions. By default it contains 9 actions and 4 behaviors. These are however just the default ones. You can download more, as well as build your own. But the built in ones actually solve a lot of problems on their own.

If you haven’t looked at Behaviors, Actions and Triggers before, there are a lot of good (and bad) blog posts about it available. But the quick run through is that a Behavior is an object that you can attach to an element to get some functionality. It can be drag-and-drop functionality or fluid motion behavior or something completely different that you have thought up. It is an all in one object that you just attach to an element and set some properties on to get some functionality.

Actions on the other hand just perform an action, i.e. it does something, when a Trigger tells it to. Both Actions and Triggers are base classes, making it easy to create your own implementations. There are a bunch of Actions available, for example CallMethodAction and ControlStoryboardAction. And there are also several Trigger types, for example DataTrigger, TimerTrigger and EventTrigger.

This makes for a very nice and clear separation of concerns, as well as a VERY flexible system. Just define what you want to happen, the Action, and when you want it to happen, the Trigger. And if you can’t find an Action or Trigger that suits you, you can just create your own. And you can create it in a nice an reusable fashion so you can pull it from project to project.

So how do we implement the above functionality using Actions? Well, if we assume the same VM, with the exposed State property, we can do it as follows.

Well, let’s start by adding a Rectangle and some visual states. This is a 1 minute job in Blend, and gives us the following Xaml

<UserControl
...
>

<Grid x:Name="LayoutRoot" Background="White">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="States">
<VisualStateGroup.Transitions>
<VisualTransition GeneratedDuration="0:0:1"/>
</VisualStateGroup.Transitions>
<VisualState x:Name="State1">
<Storyboard>
<ColorAnimation Duration="0" To="Blue" Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)" Storyboard.TargetName="rectangle" d:IsOptimized="True"/>
</Storyboard>
</VisualState>
<VisualState x:Name="State2">
<Storyboard>
<ColorAnimation Duration="0" To="Red" Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)" Storyboard.TargetName="rectangle" d:IsOptimized="True"/>
</Storyboard>
</VisualState>
<VisualState x:Name="State3">
<Storyboard>
<ColorAnimation Duration="0" To="#FF00FF56" Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)" Storyboard.TargetName="rectangle" d:IsOptimized="True"/>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Rectangle x:Name="rectangle" Fill="#FFF4F4F5" Margin="124,101,152,101" Stroke="Black"/>
</Grid>
</UserControl>

The next step is to add a VM and some Actions. The VM is the same as before, so I just hook that up to the DataContext. And to make sure we can test this, I have added some buttons that will switch the state for us…

<UserControl
...
>
<UserControl.DataContext>
<vm:StateControlViewModel />
</UserControl.DataContext>

<Grid x:Name="LayoutRoot" Background="White">
...
<StackPanel Orientation="Horizontal" VerticalAlignment="Bottom" HorizontalAlignment="Center">
<Button Content="State1" Command="{Binding SwitchState}" CommandParameter="State1" />
<Button Content="State1" Command="{Binding SwitchState}" CommandParameter="State2" Margin="10 0" />
<Button Content="State1" Command="{Binding SwitchState}" CommandParameter="State3" />
</StackPanel>
</Grid>
</UserControl>

The Actions are easily added using Blend. Just chose the Action you want, in this case GoToStateAction, from the Assets panel and drag it to the control that needs it. In this case, the VSM is connected to the root Grid, so I add it to that (it can be added to other elements and have its target changed, but let’s go with this).

As the Action is added, the Property pane lets us modify a bunch of things. In this case, the TriggerType is interesting, as well as the StateName. If you start with the simple StateName, you can just drop down the combo box to see what states are available. Let’s start with the “State1”.

The TriggerType on the other hand is a little different. Click “New…” and select a DataTrigger from the dialog. The Property pane instantly changes, and asks us for some information regarding the trigger. Binding, Comparison and Value to be precise. We want to hook up the binding first, and once again Blend helps us out. Click the little square with the black outline to the right of the Binding box and select “Data Binding…” from the context menu. In the dialog, select State and press Ok. Next we want to make sure the comparison is “Equal” and the Value is “State1”.

After this, we need to repeat the procedure for State2 and State3. Creating one Action for each of the states…

If we run the application now, the rectangle changes color nicely as the VM changes… Sweet!

But, what if I don’t want to add a ViewModel just for that simple change. What if I just wanted to get the color to change without having to have a VM. Well, you can. You can attach the Actions to the buttons themselves and use an EventTrigger instead. This way you are basically saying, when the Click event is raised, I want the VSM to change to another state. It does however mean that you need to make some tweaks and make sure that the TargetObject/TargetName as well as the SourceObject/SourceName is set to the correct objects. Otherwise you will get some weird results. But once again, Blend is there to help…

And as usual, the code is available for download here: ActionsForBlog.zip (21.40 kb)

The solution that Blend 4 is installed on your machine. And depending on where it is installed, you might have to update the reference to the Microsoft.Expression.Interaction assembly.

And as usual, questions are more than welcome through the comments or through the Contact link above.

Cheers!

Comments (1) -

Hi,

This is the approach I have been using for a long time - the advantage is that states are enums in the ViewModel.

www.silverlightshow.net/.../...to-MVVM-Part-I.aspx

Comments are closed