This is part 6 of a series of posts in which I attempt to reproduce the Zune Now Playing interface using the MVVM pattern within WPF. An introduction to the series and table of contents can be found in the first post of this series.
The Zune Now Playing interface uses a subtle animation when transitioning from one album cover to another. To help me reproduce this effect I used Blend, which provides an intuitive interface for creating keyframe-based animations using storyboard or visual states. I’ve chosen to use storyboards, which I think will make my MVVM approach easier to implement because if won’t encourage me to rely on the VisualStateManager class, which is closely associated to the world of the view.
There are a number of documented approaches on the Internet for how to handle animations when using the MVVM pattern. Some initially felt like complex implementations which put the purity of separation first, while others conceded to a small splash of code behind in the view. While the code-behind approach certainly appeals to me because it appears to offer an easier solution, I don’t want to go that way, because I’d rather focus on adhering to the guiding principles of the MVVM pattern.
Most of the suggestions only deal with kicking a single animation off, but what I’m after is more complex than that. When an Album is updated, I’ll start a fade out animation. Once the fade out completes, I’ll update the Album in the model and fire the view’s databinding updates via INotifyPropertyChanged. Finally, I’ll start a different animation to fade the album back in. With such interdependency between animations that occur in the view and state that is transitioning in the viewmodel, this quickly becomes a chicken-or-the-egg problem. I don’t want animation constructs in my viewmodel because the goal is to keep the project blendable, and to allow designers to tweak the animation in Blend.
My approach is similar to those that appear in some of the articles I’ve linked to above. I’ve ended up using a combination of an enum for the various states of the image transition process, a couple of data triggers, and a storyboard completed trigger. Here’s what the flow looks like:
Here’s how I’ve modified the CoverViewModel to accommodate the change. An external entity would call UpdateAlbum to start the process.
namespace NowPlayingMVVM.ViewModel {
public enum ImageState {
New,
BeforeChange,
Changing,
AfterChange
}
public class CoverViewModel : ViewModelBase, IAlbumConsumer {
private Album theAlbum;
private ImageState theImageState = ImageState.New;
...
public ImageState ImageState {
get { return theImageState; }
set {
theImageState = value;
NotifyPropertyChanged("ImageState");
if(value == ImageState.Changing) {
NotifyPropertyChanged("ArtworkUrl");
NotifyPropertyChanged("Name");
}
}
}
public void UpdateAlbum(Album anAlbum) {
if(theAlbum == anAlbum) return;
if (theAlbum == null) {
theAlbum = anAlbum;
ImageState = ImageState.Changing;
} else {
ImageState = ImageState.BeforeChange;
theAlbum = anAlbum;
}
}
}
}
And on the View side
<Image x:Name="image" Source="{Binding ArtworkUrl}" Stretch="Fill" Width="280" Height="280" RenderTransformOrigin="0.5,0.5">
<Interactivity:Interaction.Triggers>
<ei:DataTrigger Binding="{Binding ImageState}" Value="BeforeChange">
<!-- When ImageState becomes BeforeChange, fire FadeOut animation -->
<ei:ControlStoryboardAction Storyboard="{StaticResource FadeOut}" />
</ei:DataTrigger>
<ei:StoryboardCompletedTrigger Storyboard="{StaticResource FadeOut}">
<!-- When FadeOut completes, update ImageState to Changing -->
<ei:ChangePropertyAction PropertyName="ImageState" TargetObject="{Binding}" Value="Changing" />
</ei:StoryboardCompletedTrigger>
<ei:DataTrigger Binding="{Binding ImageState}" Value="Changing">
<!-- When ImageState becomes Changing, fire FadeIn animation -->
<ei:ControlStoryboardAction Storyboard="{StaticResource FadeIn}" />
</ei:DataTrigger>
<ei:StoryboardCompletedTrigger Storyboard="{StaticResource FadeIn}">
<!-- When FadeIn completes, update ImageState to AfterChange -->
<ei:ChangePropertyAction PropertyName="ImageState" TargetObject="{Binding}" Value="AfterChange" />
</ei:StoryboardCompletedTrigger>
</Interactivity:Interaction.Triggers>
</Image>
I don’t really love this approach because although it technically doesn’t require the View and ViewModel to know about each other, the two are still tightly bound by the use of the enum and the order of events. When looking at the ViewModel, the trigger logic can only really be understood with a good grasp of what is happening in the View. In addition, while the View still renders in Blend, the animations don’t fire, so I’m unable to test and refine them. Because I’m trying to create a common animation for each of my cover views, I’ve had to resort to creating a TestAnimation user control where I can use the tools to develop the animation I want, before copying the resource to my shared ResourceDictionary. Each of the cover views is a different size, so I can’t take advantage of FrameworkElement.Width, and I have to use RenderTransforms instead.
I wonder if my objections to this approach are petty and over-thought. This solution doesn’t feel right, but it will work for the time being. As I learn more about WPF, I’ll refine my understanding of what the pluses and minuses of this approach are.