Tuesday, July 28, 2009

[2009.07.28] WPF Controls and the Visual State Manager [VSM]

I was out of the Expression Blend game for a little bit while I was learning Haskell. One of the new (at least to me) additions to Silverlight and Blend was the Visual State Manager (VSM). I saw a little bit of it from the Mix 2009 talks and decided to look into it some more. Microsoft is working on bridging the gap between Silverlight and WPF, and the VSM takes us one step closer in that direction by extending the templating ability of the .NET Framework. Therefore, controls using VSM will work both in WPF and Silverlight the same way.

From what I have seen, VSM basically provides another method of triggering property changes. As of now (July 2009) we can use Triggers, Visual State Manager or Behaviors to Create/Edit templates. For instance, before VSM, if you wanted to template your own button in Blend, then you have to set property triggers to deal with say MouseOver events. Then you would setup Actions for Activating/Deactivating which amounts to pointing to a storyboard to perform the actions. Well VSM does basically the same thing, but from what I have seen, some of the details are taken care of behind the covers. When using VSM you do not explicitly setup events to respond to property changes, although in some cases you will have to. I will get into that later. Note that you do not have to use VSM. If you are happy with triggers in your templating, then you are good to go as the results are the same. However, I find using the VSM provides for a clean and simple experience with very little complications. To me, simple is always good.

Note: I am using Expression Blend 3, Silverlight 3.0, WPF ToolKit (June 2009), Visual Studio 2010 B1 with .NET 3.5 SP1. For some reason, .NET 4.0 + VS 2010B1 + Blend 3 does not play nice just yet so I had to fall back on .NET 3.5.

I will illustrate two methods of using the VSM in this post. The first is the no-code approach using built in states and the other is the minimal-code approach where you define states that is not necessarily baked into the control.

StateGroups and States

The one thing I noticed were that all the demos were done in Silverlight. Since the idea is having WPF and Silverlight play nice, I will go at this from the WPF point of view. I suppose the basic pattern is to start the project in Visual Studio, then send it to Blend for visual authoring. After you create the VS project, take a look at the References folder and make a note of all the referenced assemblies. Fire up the project in Blend and take a look at the references and it should be the same. Now if you go to the States tab and hit the "Create New State Group" button then go back to the Reference folder, you will notice an automatic addition to the WPFToolkit.dll assembly as this is where the VSM lives.

I will use Expression Blend 3.0 (as of July 2009) to wire up a simple WPF application, with just one button. The idea is to edit the template of the button, add states to it and see how it all works.

While Silverlight controls have built in support for states (as is states are already defined and working), WPF controls do not. For instance, let's take a quick look at the template for a Button both in WPF and Silverlight in Expression Blend. Place the control on the artboard then:
Right Click -> Edit Template -> Edit a Copy.... If you take a look at the States tab in Blend for both projects, you will see that the Silverlight template is populated with a predefined set of States, whereas the WPF template has none. This is shown in Fig. 1.

Fig. 1. WPF vs. Silverlight template editing showing States

Even though WPF does not come with pre-defined States, it is not that difficult to define and implement our own. This can be done in two ways:

  1. Either use the reflector tool to look at the assembly of the control you are templating and find the defined states.
  2. Use Blend to start a Silverlight project, drag your control to the artboard and edit a copy of its template. This will populate all the states available for that control.

To me it is easiest to simply "borrow" the states using the second approach. From Fig. 1, it can been seen that the Silverlight button has two VisualStateGroup state groups, called CommonStates and FocusStates. Each group has a set of defined VisualStates objects. Groups exist to separate orthogonal or mutually exclusive states, meaning that states that exist in one group cannot act together. By example, this says that, we cannot be both in the Pressed and MouseOver states at the same time, hence they are orthogonal to each other. However, it is possible to be both in the Focus and MouseOver states. So let's go ahead and implement the states that exist on the Silverlight button to the WPF button.

Since we started off with no states, the first thing to do is to create via the Add state group button, two VisualStateGroup objects and name them as in Fig. 2. Notice also there is a Default transition option. This allows you to set the time to transition between states in each group. However, once you define the states themselves, you can then explicitly define transitions for that state.

Fig. 2. Adding state groups

Now that we have the states, simply add the states and name them to each state group. As soon as you add a state, recording for that state begins immediately, as shown in Fig. 3 by the red outline of the artboard. A red circle next to the name of the state indicates that you are recording changes for that state. To turn off recording, simply select the Base state which is added automatically to you when you first create a state group.

Fig. 3. Adding states

For now, continue adding states and turn off recording for each as they are added. We will deal with state recording next. The final result should look something like Fig. 4.

Fig. 4. Finished adding states

Before we do state recording, let's change the default template a little. Instead of the ClassicBorderDecorator as the root of the element, I will use a Grid so that I can add child elements to it. Then to the grid I will add a Border which will be used to indicate the state. To start off with, I will set the Opacity property to 0. Now let's look at the states. Basically, all we are doing is figuring out how we want the control to look at each state.


Normal: This is the same as the Base state, that is, no interaction with the control. So here, we leave it as it is.

MouseOver: This will be what the control looks like when the user passes the mouse cursor over it. Click on the Normal state and notice that storyboard recording has started. Next, select the element that you want to modify when this state changes. Here I will just adjust the gradient of the grid named background. As soon as you set the properties you want, you will notice in the Objects and Timeline window that key frames has been created to indicate which element has been modified. Notice thus far, I have not done any sort of animation on the property, so that is why the key frame is at time = 0. See this in Fig. 5.

Fig. 5. Updating the control to respond to the MouseOver state

Pressed: Again, Click the Pressed state, turn on recording, select the element/properties to modify then turn off recording. I will not go through each stage as they are the same. In this state I will change the background of the root grid.

Disabled: I just darkened the background of the grid to give the illusion of being disabled.


Focused: I changed the opacity of the border to indicate focus.

Unfocused: Same as the base or normal state.

Run the application and notice that you can interact with your button and you have not typed any code in as yet! The result of all the states is shown in Fig. 6. There is no point in me adding the generated XAML here. If you follow along as I explained, you will get the same result.

Fig. 6. Summary of states


Transitions gives you the opportunity to exactly how to specify transition from one state to another. Refer to Fig. 1 and notice that just under each group state name, we have a Default transition and a textbox to set the time of the transition. Whatever we set here would be the time taken by each state of this group to transition between each other. If on the other hand you want to do a transition for a specific state, then click the Add transition for that state and define the transition as seen in Fig. 7.

Fig. 7. Setting transitions for each state

As you can see, you are given a choice of how your transition should behave. In addition, you are not restricted to using just one transition.

No comments: