This is the third post in a four part series on Silverlight 2’s Parts & States control model.
Last time, you learned how to reskin an existing control using VisualStateManager. In this post, you’ll see how to build up a Parts & States-based custom control. We’ll also explore how you can create more sophisticated visual transitions.
(Series Link: Part 1, Part 2, Part 3, Part 4)
VisualStateManager
We’ve saw it briefly in the last post, but let’s formally introduce VisualStateManager.

VisualStateManager is the class responsible for control visual state management. The “visual” modifier in that sentence is important - the control code remains in charge of the logical state machine.
VSM exposes two main pieces of PME:
- a VisualStateGroups attached property
- This property is set on the control template’s root visual and contains all the visual states & transitions for that skin
- a static GoToState() method
- This method causes VisualStateManager to transition the control’s visuals from one visual state to another.
Last time, we concentrated on the VisualStateGroups property in XAML. Today, we’ll dig into how the control code leverages that GoToState() method.
WeatherControl
The custom control that we’ll be looking at today is a simple WeatherControl. The shell of the control code can be found below. (Note: For readability, I’ve collapsed some of the code snippets. You can find the full sample code here.)
1: public class WeatherControl : Control
2: {
3:
4: public WeatherControl()
5: {
6: DefaultStyleKey = typeof(WeatherControl);
7: }
8:
9: // OnApplyTemplate()
10: public override void OnApplyTemplate()
11: {
12: base.OnApplyTemplate();
13: }
14:
15: // Temperature DP
16: public static readonly DependencyProperty TemperatureProperty = DependencyProperty.Register(“Temperature”, typeof(string), typeof(WeatherControl),null);
17: public string Temperature
18: {
19: get { … }
20: set { … }
21: }
22:
23: // Condition DP
24: public static readonly DependencyProperty ConditionProperty = DependencyProperty.Register(“Condition”, typeof(Condition), typeof(WeatherControl), new PropertyMetadata(new PropertyChangedCallback(WeatherControl.OnConditionPropertyChanged)));
25: public Condition Condition
26: {
27: get { … }
28: set { … }
29: }
30:
31: // ConditionDescription DP
32: public static readonly DependencyProperty ConditionDescriptionProperty = DependencyProperty.Register(“ConditionDescription”, typeof(string), typeof(WeatherControl), null);
33: public string ConditionDescription
34: {
35: get { … }
36: set { … }
37: }
38:
39: // Property change notification
40: private static void OnConditionPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
41: {
42: WeatherControl weather = d as WeatherControl;
43: …
44: weather.OnWeatherChange(null);
45: }
46:
47: // OnWeatherChange virtual
48: protected virtual void OnWeatherChange(RoutedEventArgs e)
49: {
50: }
51:
52: }
You can see that our WeatherControl…
- is a custom control, deriving from Control.
- defines its own built-in style, as indicated by the DefaultStyleKey.
- has 3 public dependency properties:
- Temperature
- Condition
- ConditionDescription
In order to make our WeatherControl skinnable with VSM, we need to:
- define a control contract
- discover & manipulate parts
- wire up appropriate state changes using VisualStateManager
Here we go!
Defining the Control Contract
The control code is responsible for documenting the control contract. This means it should declare any and all expected Parts and States. Control contract declaration is done using class level metadata:
1: [TemplatePart(Name="Core", Type=typeof(FrameworkElement))]
2:
3: [TemplateVisualState(Name="Normal", GroupName="CommonStates")]
4: [TemplateVisualState(Name="MouseOver", GroupName="CommonStates")]
5: [TemplateVisualState(Name="Pressed", GroupName="CommonStates")]
6:
7: [TemplateVisualState(Name="Sunny", GroupName="WeatherStates")]
8: [TemplateVisualState(Name="PartlyCloudy", GroupName="WeatherStates")]
9: [TemplateVisualState(Name="Cloudy", GroupName="WeatherStates")]
10: [TemplateVisualState(Name="Rainy", GroupName="WeatherStates")]
11: public class WeatherControl : Control
12: {
13: …
14: }
In the above snippet, there are two attribute classes:
- TemplatePartAttribute
- Specifies the name of the part & expected type
- TemplateVisualStateAttribute
- Specifies the name of the state & its associated state group
This metadata is not used by the runtime. However, it is leveraged by tools like Expression Blend for their skinning support, so it’s important to include.
These attributes on the WeatherControl give rise to the following control bill of materials:

Now, let’s see how the control code manipulates Parts.
Discovering Parts
Parts are named elements in the template and need to be manually discovered by the control code. This is done in the OnApplyTemplate() virtual, which is called whenever a new template is applied.
1: // OnApplyTemplate
2: public override void OnApplyTemplate()
3: {
4: base.OnApplyTemplate();
5:
6: CorePart = (FrameworkElement)GetTemplateChild(“Core”);
7: }
8:
9: // private CorePart property
10: private FrameworkElement CorePart
11: {
12: get
13: {
14: return corePart;
15: }
16:
17: set
18: {
19: FrameworkElement oldCorePart = corePart;
20:
21: if (oldCorePart != null)
22: {
23: oldCorePart.MouseEnter -= new MouseEventHandler(corePart_MouseEnter);
24: oldCorePart.MouseLeave -= new MouseEventHandler(corePart_MouseLeave);
25: oldCorePart.MouseLeftButtonDown -= new MouseButtonEventHandler(corePart_MouseLeftButtonDown);
26: oldCorePart.MouseLeftButtonUp -= new MouseButtonEventHandler(corePart_MouseLeftButtonUp);
27: }
28:
29: corePart = value;
30:
31: if (corePart != null)
32: {
33: corePart.MouseEnter += new MouseEventHandler(corePart_MouseEnter);
34: corePart.MouseLeave += new MouseEventHandler(corePart_MouseLeave);
35: corePart.MouseLeftButtonDown += new MouseButtonEventHandler(corePart_MouseLeftButtonDown);
36: corePart.MouseLeftButtonUp += new MouseButtonEventHandler(corePart_MouseLeftButtonUp);
37: }
38: }
39: }
To find a named element inside of the template, you use the GetTemplateChild() helper method.
In the above example, we discover the “Core” part, which we will use to determine when the control should go into the MouseOver or Pressed states. Note that the setter logic is resilient to the Core part not being in the template. This is important, because a control needs to be robust enough to handle a part that is missing or not yet been added.
Initiating State Changes
The control code is responsible for telling VisualStateManager when a visual state change should occur. Therefore, it must maintain the logical state machine that is associated with the visual state machine.
All of Silverlight 2’s built-in controls create a simple helper method to assist with the state changes. We recommend that you follow a similar pattern:
1: // GoToState() helper
2: private void GoToState(bool useTransitions)
3: {
4: // Go to states in NormalStates state group
5: if (isPressed)
6: {
7: VisualStateManager.GoToState(this, “Pressed”, useTransitions);
8: }
9: else if (isMouseOver)
10: {
11: VisualStateManager.GoToState(this, “MouseOver”, useTransitions);
12: }
13: else
14: {
15: VisualStateManager.GoToState(this, “Normal”, useTransitions);
16: }
17:
18: // Go to states in WeatherStates state group
19: if (Condition == Condition.PartlyCloudy)
20: {
21: VisualStateManager.GoToState(this, “PartlyCloudy”, useTransitions);
22: }
23: else if (Condition == Condition.Sunny)
24: {
25: VisualStateManager.GoToState(this, “Sunny”, useTransitions);
26: }
27: else if (Condition == Condition.Cloudy)
28: {
29: VisualStateManager.GoToState(this, “Cloudy”, useTransitions);
30: }
31: else
32: {
33: VisualStateManager.GoToState(this, “Rainy”, useTransitions);
34: }
35: }
The GoToState helper method contains a series of if statements which determine the current visual states. It then tells VisualStateManager to initiate the appropriate state change. This is done with the static public static bool VisualStateManager.GoToState(Control control, string stateName, bool useTransitions) method.
As you can see, this method…
- has 3 parameters:
- control: instance of the control
- stateName: name of the visual state to go to
- usetTransitions: flag to determine whether transitions should be run in this state change
- returns a bool
- It returns true if the state name was found and false otherwise.
- is a no op if…
- the control was already in the passed in visual state
- the visual state cannot be found
Most control authors will call their GoToState() helper in 3 places:
- OnApplyTemplate() with no transitions.
- When the control first appears, we generally want it to just appear in the appropriate state, and not transition into it.
- In certain property change notification handlers
- In certain event handlers
For our WeatherControl, we add these calls:
1: // OnApplyTemplate
2: public override void OnApplyTemplate()
3: {
4: base.OnApplyTemplate();
5:
6: CorePart = (FrameworkElement)GetTemplateChild(“Core”);
7:
8: GoToState(false);
9: }
10:
11: // Property Change Notifications
12: protected virtual void OnWeatherChange(RoutedEventArgs e)
13: {
14: GoToState(true);
15: }
16:
17: // Event Handlers
18: void corePart_MouseEnter(object sender, MouseEventArgs e)
19: {
20: isMouseOver = true;
21: GoToState(true);
22: }
23:
24: void corePart_MouseLeave(object sender, MouseEventArgs e)
25: {
26: isMouseOver = false;
27: GoToState(true);
28: }
29:
30: void corePart_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
31: {
32: isPressed = true;
33: GoToState(true);
34: }
35:
36: void corePart_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
37: {
38: isPressed = false;
39: GoToState(true);
40: }
For the WeatherControl, we need to initiated state changes when:
- the template is first applied
- the Condition property is changed
- mouse events are raised from the Core part
Adding a Built-In Style
So now we have our control logic!
I’ve cooked up a very fun (if I do say so myself) ControlTemplate to show off our WeatherControl. The “fun” makes the template a bit long, however. Here’s the editted version:
1: <!– VisualStateManager –>
2: <vsm:VisualStateManager.VisualStateGroups>
3:
4: <!– CommonStates StateGroup–>
5: <vsm:VisualStateGroup x:Name=”CommonStates”>
6:
7: <!– CommonStates States–>
8: <vsm:VisualState x:Name=”Normal”/>
9: <vsm:VisualState x:Name=”MouseOver” Storyboard=”{StaticResource Glow}”/>
10: <vsm:VisualState x:Name=”Pressed” Storyboard=”{StaticResource Burn}”/>
11:
12: <!– CommonStates Transitions–>
13: <vsm:VisualStateGroup.Transitions>
14: <vsm:VisualTransition Duration=”0:0:.6″/>
15: <vsm:VisualTransition To=”Pressed” Duration=”0:0:.4″/>
16: <vsm:VisualTransition From=”Pressed” Duration=”0:0:.4″/>
17: </vsm:VisualStateGroup.Transitions>
18:
19: </vsm:VisualStateGroup>
20:
21: <!– WeatherStates StateGroup–>
22: <vsm:VisualStateGroup x:Name=”WeatherStates”>
23:
24: <!– WeatherStates States–>
25: <vsm:VisualState x:Name=”Sunny”/>
26: <vsm:VisualState x:Name=”PartlyCloudy” Storyboard=”{StaticResource PartlyCloudyStoryboard}”/>
27: <vsm:VisualState x:Name=”Cloudy” Storyboard=”{StaticResource CloudyStoryboard}”/>
28: <vsm:VisualState x:Name=”Rainy” Storyboard=”{StaticResource RainyStoryboard}”/>
29:
30: <!– WeatherStates Transitions–>
31: <vsm:VisualStateGroup.Transitions>
32: <vsm:VisualTransition Duration=”0:0:.3″/>
33: </vsm:VisualStateGroup.Transitions>
34:
35: </vsm:VisualStateGroup>
36: