Wednesday, November 2, 2011

WPF transitions using pixel shaders and Blend SDK

WPF transitions are a very nice way to turn standard, boring application interactions into great user experiences, and show the author pays attention to details. There are basically two ways of implementing transitions in WPF. You can do a manual transition using RenderTransform to modify the visuals’ position, scaling and rotation. Throwing in an opacity animation would improve the effect. This option is talked more in-depth here. However, there is a couple of problems with this approach. The biggest issue is that such transitions are CPU bound, since the transition is calculated on the main processor. The other problem is more prosaic – they are relatively hard to implement. The other way is to utilize pixel shaders to blend between two visuals’ images making the illusion of transition. Transitions done this way are GFX bound, offloading the CPU which can perform other useful tasks. Besides, they look just awesome, as you shall see in a moment :) There is a small problem associated with this approach, though. To write custom pixel shader, some GFX knowledge accompanied by HLSL might be required. Crafting nice effect probably would also require a bit of math :) Thankfully, there are some nice libraries packed with effects ready to use! So no worries, we will not be talking math here!
In this post I will discuss how to build a TransitionControl, which beautifully animates transitions from one visual state into the other. To demonstrate the effect, I updated my wizard demo (discussed here and here) so that transitions between consecutive steps are nicely animated. I will also show what it takes to add transition effect to regular TabControl. The following screens show the slide transition between two wizard steps.
And the following shows a transition within TabControl.

Pixel shader libraries
Pixel shaders were introduced in .NET Framework 3.5 SP1. By this time, Microsoft released really cool library called WPF Effects Library. The library is composed of two types of components: effects and transitions. Effects are successors to well known bitmap effects, and provide a way to alter how a visual is rendered on the screen (see this video). Transitions, on the other hand, provide a means to.. transition from one visual state to the other. Easy, huh ? We will leverage them to build robust TransitionControl.
These days the project seems not to be much updated, the latest version is 2.0 beta which targets .NET 3.5, but works with .NET 4.0 and Visual Studio 2010. The good news is that although this project is not longer maintained, most of the effects and transitions made its way to Blend 4.0 SDK! They are available in Microsoft.Expression.Effects.dll assembly.
Okay, so I introduced transitions, just show how to use them and probably we’re done here, right ? it turns out we’re not quite done here. Lets see what is the API for using a transition effect. The base TransitionEffect class, from which all transitions derive, is located in Microsoft.Expression.Interactions.dll assembly, and its public API is defined as follows:

The class inherits ShaderEffect, which makes it essentially a regular pixel shader effect. So it can be assigned to the Effect property of any UIElement. There are also two important properties: OldImage and Progress. The OldImage brush defines the look of the old visual, and the Progress, between 0.0 and 1.0, denotes where we are within the transition. So the flow when using any TransitionEffect is as follows:

Choose any transition effect and apply it to any UIElement which is the target visual we want to transition to

Get the VisualBrush which depicts the visual we transition from, and assign it to the OldImage property

Animate Progress property from 0.0 to 1.0 to perform the actual transition

As you can see, this is not very complicated, but far from being convenient. And this is where TransitionControl comes to play (I believe Telerik offers similar control).

Meet the TransitionControlTransitionControl encapsulates the above three steps and provides a nice API to consume by the client. Let’s think for a moment, what is the most convenient API for the TransitionControl ? Yes, you’re right, it’s a ContentControl! ContentControl provides a Content property which is almost everything we care about. Whenever we change the Content property to something else, we expect to get a nice transition which smoothly goes from one visual state to the other. Having said that, let’s see the API of the TransitionControl.

You can see that the TransitionControl is essentially a ContentControl. This is good, because, whenever you use ContentControl, you can swap it for TransitionControl and get the transitions for free! The second important thing is how actually TransitionControl knows about particular transition we want to use ? This is where TransitionSelector comes in. It is an abstract class which behaves similarly to DataTemplateSelector. It defines single method as show below.

You implement this class and hand over a new instance to TransitionControl. You get both the old and new contents, so it is possible to write dynamic logic which selects transition based on a content. Nice, huh ? There are also two properties which drive the behavior of the transition. Duration determines the length of the transition, and EasingFunction allows to supply easing function :)

How does it work ?
The transition kicks of when the Content property changes. If the previous content was empty, of course no transition occurs. But if both old and new contents are presents, transition is triggered. The “magic” sits in the AnimateContent method, which is responsible for going through the three steps outlined previously.

How to make transition aware TabControl
This task is super easy task! Just extract the default template for TabControl, and remove ContentPresenter with name PART_SelectedContentHost and provide the following XAML.

Conclusion
In this post I presented how using pixel shader effects create really cool looking, GFX bound transitions. Since the control responsible for wiring up everything is a ContentControl, you can use it wherever you use regular ContentControl, and you will get the transitions for free.