Right now I am working on a Silverlight project for my company. In that project, as in most projects with Silverlight I need to run some animations. And since I’m working with MVVM this becomes a little cumbersome and complicated. I don’t want my view to be dependent on the viewmodel. So the view cant tell the model what storyboards to play. And I don’t want the viewmodel to be dependent on the view either. So i don’t want to give the viewmodel a referens to the view. I guess I could get some separation using interfaces, but it still felt a little off… So I thought a little about this, and then I Googled it. Do you know what I found when googling for “patterns mvvm animations”. Nothing really useful. A bunch of questions. I even tried to search for WPF and tried to leverage the WPF delelopers knowledge…no luck… So I had to figure something our myself. And I think I have actually found a pretty nice separation by using a Storyboard manager object.
The manager declares a attached property called ID. I actually wanted it to be Name, but adding an attached property with that name caused problems in VS apparently (2008 with SL3). The auto generated cs file all of the sudden started to register duplicate storyboards…I thought that that was the reason for placing the Name attribute in a separate namespace…well…apparently not… Anyhow. The manager then contains a dictionary that keeps track of all storyboards registered with an id. It also has a method called PlayAnimation. The play animation method takes an id, a callback and a state object. Calling that method will locate the correct storyboard, register for its Completed event, play it and then call the callback delegate when it is completed. It will also pass the state object to the callback as this might be helpful…
If it can’t find a storyboard with the specified id, it calls the callback straight away. So the viewmodel don’t have to bother if there is a storyboard with that name or not. I guess this sort of follows the “parts and states” pattern that Microsoft uses for their controls…
It is real simple, but gets the job done. I might extend it in the future to support stopping and querying the state of the storyboard as well. But for now, this is what I needed. The code for the manager looks something like this
public static class StoryboardManager
{
public static DependencyProperty IDProperty =
DependencyProperty.RegisterAttached("ID", typeof(string), typeof(StoryboardManager),
new PropertyMetadata(null, IDChanged));
static Dictionary<string, Storyboard> _storyboards = new Dictionary<string, Storyboard>();
private static void IDChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
Storyboard sb = obj as Storyboard;
if (sb == null)
return;
string key = e.NewValue as string;
if (_storyboards.ContainsKey(key))
_storyboards[key] = sb;
else
_storyboards.Add(key, sb);
}
public static void PlayStoryboard(string id, Callback callback, object state)
{
if (!_storyboards.ContainsKey(id))
{
callback(state);
return;
}
Storyboard sb = _storyboards[id];
EventHandler handler = null;
handler = delegate { sb.Completed -= handler; callback(state); };
sb.Completed += handler;
sb.Begin();
}
public static void SetID(DependencyObject obj, string id)
{
obj.SetValue(IDProperty, id);
}
public static string GetID(DependencyObject obj)
{
return obj.GetValue(IDProperty) as string;
}
}
public delegate void Callback(object state);
A nice little thing to look specifically at is this
EventHandler handler = null;
handler = delegate { sb.Completed -= handler; callback(state); };
sb.Completed += handler;
By creating an EventHandler explicitly makes it possible to disconnect it when it is called. If I had just added an anonymous method, it would have been called over and over again every time that storyboard played, which is not something I want. I want to have the callback called for this play only. And I also have to explicitly declare it as null before setting it to the anonymous method. If I don’t, I can’t reference it in my anonymous method. That’s the reason for the cumbersome, two line declaration…
Using it looks like this in Xaml
<UserControl x:Class="MyApplication"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:StoryboardManager="MyApplication.Utilities">
<Grid x:Name="LayoutRoot" Background="White">
<Grid.Resources>
<Storyboard x:Key="MyStoryboard" StoryboardManager:StoryboardManager.ID="MyAnimation"></Storyboard>
</Grid.Resources>
</Grid>
</UserControl>
And using it in my viewmodel looks like this
StoryboardManager.PlayStoryboard("MyStoryboard", (o => DoSomething()), myObject);
Kind of sweet right. No direct dependencies between the viewmodel and the view. Keeping the viewmodel unit testable. Well…I like it at least. It solved a bit of my problems. This with some ICommands and the CommandManager got my viewmodel to a place where I like it to be…
Cheers!
Update:
After a couple of comments about it not working, I have created a demo solution to test it in. The code works perfectly fine… After having thought about it a bit more, I am not 100% sure that this is the best way to handle it. But it does work… So anyhow… here is the code: MVVMAnimation.zip (16.71 kb)