Wednesday, July 29, 2009

[2009.07.29] WPF custom UserControl and the Visual State Manager [VSM]

In my previous post, I went through the process of adding Visual States to a WPF control by looking at the template of the corresponding Silverlight control. The upside to this was there was no need to write any code. However, in order to create custom states, some code is required as will be seen.

I will use Blend 3.0 to create a WPF UserControl and add some states and event handlers to it.

Fire up Blend, do File -> New Project... -> and when the New Project dialog pops up, select WPF application. Under the Projects tab, right click and select Add New Item... which opens up the New Item dialog. Choose UserControl and give it a name or leave the default name (UserContol1.xaml) if you are satisfied with it as in Fig. 1. I will stick with the default name.

[2009.07.29].01.Add.UserControl  
Fig. 1. Adding a WPF UserControl to Blend workspace

I am doing this in Blend just to illustrate the visual side of authoring a control and adding visual states to it. In order to really add functionality to the control, Visual Studio is the tool of choice. Here is a little background on creating custom controls. There are three ways to build custom controls:

  1. Derive from UserControl: This is the simplest method and it lets you compose controls the same way you write regular WPF apps, by using existing controls. While a UserControl derived control supports rich content, styles and triggers, it does not allow the user to use DataTemplate or ControlTemplate to customize how your control looks.
  2. Derive from the Control class. This is the method used by the existing WPF controls. This method follow the Model-View-ViewModel (MVVM) pattern by separating the UI and the logic. In addition, you can use DataTemplate or ControlTemplate to customize the control's appearance and it also supports themes.
  3. Derive from FrameworkElement. This gives you low level control over your authoring process in that you can support your own rendering logic and control the appearance of your control in a more fine grained manner.

If you followed the few steps above, your Blend environment will look almost the same as if you were writing a WPF application. However, instead of the typical Window root element filled with a White Brush, the root element is of type UserControl with no background brush. From here you can go ahead and create a control just like you create a WPF application.

In keeping with my "simple" mentality, I will first delete the default Grid named LayoutRoot and create a structure shown in Fig. 2. It is to make a simple traffic light.

[2009.07.29].02.Traffic.light.UserControl
Fig. 2. Structure and look of the traffic light user control

Now I will add states for ellipse I used to create the "lights". There is only one state group, which I will call MouseOverGroup. I am only concerned with how the control will look in two states: Normal and MouseOver. However, since I want to react to state changes for 3 separate objects within my control, I have to define separate MouseOver states for each of them. Now, because we are not really extending an already existing control with baked in states, we have to add our own as shown in Fig. 3.

[2009.07.29].03.State.definitions
Fig. 3. States used for this control

Notice also that I have added a transition for the Normal state. So the * -> Normal transition means "from any state to the Normal state".

Now we have to actually add the property changes we want to see reflected when the control enters that state. As soon as you click on the state (ex: MouseOverRed), state recording begins for that state. Now select which object you want to manipulate and do so. As shown in Fig. 4, I am adjusting the Fill property of the ellipse representing the red light. Here I have used the storyboard to create an animation from the original color to a more deeper red. I dragged the timing indicator for the animation to 0.5 seconds and changed the property. Notice at 0.5 seconds, a key frame was created. Note: Don't forget to save your file before switching to recording another state!

[2009.07.29].04.Adding.state  
Fig. 4. Adding state information for MouseOverRed

If you add this control to your application, you are in for a little surprise. It does not work! Well that makes sense since we didn't wire up any interaction to react to our state changes. Remember, since this is a custom control, WPF has no knowledge of the states you create. You have to use code to move between states.

Note: To find the control you create, go to the main application window and under the Assets tab, there is a Projects option. UserControl1 is there for you to drag into the application.

Now let's respond the MouseEnter and MouseLeave event for each ellipse object. For instance, when the mouse passes over the red ellipse, it will transition to the MouseOverRed state and when it passes off, it will go back to the Normal state. This applies to the others as well. To add these event handlers in, select the element we are interested in and switch over to the Events pane. Find the events we are interested in and enter names for them as shown in Fig. 5.

[2009.07.29].05.Adding.event.handlers
Fig. 5. Adding event handlers

As soon as you enter the last name and hit Enter on your keyboard, you will immediately jump to the code behind file, UserControl1.cs for the UserControl1.xaml control. In each handler, we want to call the static GoToState method of the VisualStateManager class. The definition of the GoToState method is given by:

public static bool GoToState(Control control, string stateName, bool useTransitions)

Therefore, for our "red" mouse over state we have something like:

private void red_MouseEnter(object sender, System.Windows.Input.MouseEventArgs e)

{

    VisualStateManager.GoToState(this, "MouseOverRed", true);

}

 

private void red_MouseLeave(object sender, System.Windows.Input.MouseEventArgs e)

{

    VisualStateManager.GoToState(this, "Normal", true);

}

Now all you have to do is repeat what you have done for the red light with the other two, add in event handler names and in each handler, put in the appropriate state to transition to and you have a stateful traffic light that responds to MouseEnter and MouseLeave events to change the state of the control. The XAML for all this isn't terribly interesting and it is auto generated so there is no point in adding it here.

1 comment:

Anonymous said...

BRILLIANT!