Just a note to say I’ve updated my FlickrViewr for Silverlight Beta2. (All of the controls now use VisualStateManager, too!)
Enjoy!
(See the original post for more info & source code.)
Just a note to say I’ve updated my FlickrViewr for Silverlight Beta2. (All of the controls now use VisualStateManager, too!)
Enjoy!
(See the original post for more info & source code.)
This is the second post in a four part series about managing parts & states in Silverlight 2 controls.
Today, we’ll put into practice the concepts you learned last time and walk through how to reskin a CheckBox. (If you haven’t already, please be sure to read part 1 of this series.)
Note: I’ve shortened the XAML snippets in this post for readability. You can download the full sample code here.
(Series Link: Part 1, Part 2, Part 3, Part 4)
A CheckBox ControlTemplate
ControlTemplates define the visuals for a custom control. The CheckBox ControlTemplate that we’ll be expanding on in this post is below.
1: <ControlTemplate TargetType="CheckBox">
2: <StackPanel x:Name="Root" ...>
3:
4: <!-- OuterBorder -->
5: <Border Width="20" Height="20" ... >
6:
7: <!-- InnerBorder -->
8: <Border x:Name="InnerBorder" ... >
9:
10: <Grid>
11: ...
12:
13: <!-- Higlight-->
14: <Border x:Name="HighlightBorder" ... />
15:
16: <!-- Glow -->
17: <Rectangle x:Name="Glow" Opacity="0" ... />
18:
19: <!-- Checkmark Graphic-->
20: <Path x:Name="Checkmark" Opacity="0" ... />
21:
22: <!-- Indeterminate Rect-->
23: <Rectangle x:Name="IndeterminateRect" Opacity="0" ... />
24: </Grid>
25:
26: </Border>
27: </Border>
28:
29: <!-- ContentPresenter -->
30: <ContentPresenter .../>
31:
32: </StackPanel>
33: </ControlTemplate>
Time to fix that!
Adding VisualStates & VisualStateGroups
As we discussed last time, the Parts & States Model introduces the notion of visual states and visual state groups.
In Silverlight 2, we wanted these ideas to be first class concepts and so we have made them into their own classes: VisualState and VisualStateGroup. They are managed by the VisualStateManager, which runs the visual state machine for the control.
Let’s see how to add VisualStates & VisualStateGroups to our CheckBox skin!
Adding VisualStateGroups to the CheckBox ContrlolTemplate
CheckBox has two primary state groups (it has 3 state groups total, but for simplicity, we’re going to ignore the focus group):
You add these state groups to the ControlTemplate like this:
1: <ControlTemplate TargetType="CheckBox">
2:
3: <!-- Root Visual -->
4: <StackPanel x:Name="Root" ... >
5:
6: <!-- VisualStateManager-->
7: <vsm:VisualStateManager.VisualStateGroups>
8:
9: <!-- CommonStates StateGroup-->
10: <vsm:VisualStateGroup x:Name="CommonStates">
11:
12: </vsm:VisualStateGroup>
13:
14:
15: <!-- CheckStates StateGroup-->
16: <vsm:VisualStateGroup x:Name="CheckStates">
17:
18: </vsm:VisualStateGroup>
19:
20: </vsm:VisualStateManager.VisualStateGroups>
21:
22: <!-- Rest of Template -->
23: ...
24:
25: </StackPanel>
26: </ControlTemplate>
27:
As you can see from this XAML, to add state groups you need to…
Now that we’ve added the VisualStateGroups, the next step is to populate them.
Adding VisualStates to a VisualStateGroup
CheckBox has 7 states spread across these two state groups. Below is the CheckBox blueprint for its default skin.
Let’s start by adding the four visual states to our skin’s CommonStates:
1:
2: <vsm:VisualStateGroup x:Name="CommonStates">
3:
4: <!-- Normal State -->
5: <vsm:VisualState x:Name="Normal"/>
6:
7: <!-- MouseOver State -->
8: <vsm:VisualState x:Name="MouseOver">
9: <Storyboard>
10: <DoubleAnimation
11: Storyboard.TargetName="Glow"
12: Storyboard.TargetProperty="Opacity"
13: Duration="0" To="1"/>
14: </Storyboard>
15: </vsm:VisualState>
16:
17:
18: <!-- Pressed State -->
19: <vsm:VisualState x:Name="Pressed">
20: <Storyboard>
21: <DoubleAnimation
22: Storyboard.TargetName="HighlightBorder"
23: Storyboard.TargetProperty="Opacity"
24: Duration="0" To=".6"/>
25: <ColorAnimation
26: Storyboard.TargetName="InnerBorder"
27: Storyboard.TargetProperty="(Border.BorderBrush).(GradientBrush.GradientStops)[0].(GradientStop.Color)"
28: Duration="0" To="#FF000000"/>
29: <ColorAnimation
30: Storyboard.TargetName="InnerBorder"
31: Storyboard.TargetProperty="(Border.BorderBrush).(GradientBrush.GradientStops)[1].(GradientStop.Color)"
32: Duration="0" To="#FF000000"/>
33: </Storyboard>
34: </vsm:VisualState>
35:
36: <!-- Disabled State -->
37: <vsm:VisualState x:Name="Disabled">
38: <Storyboard>
39: <DoubleAnimation
40: Storyboard.TargetName="Root"
41: Storyboard.TargetProperty="Opacity"
42: Duration="0" To=".7"/>
43: </Storyboard>
44: </vsm:VisualState>
45:
46: </vsm:VisualStateGroup>
As seen above, the VisualState element…
contains a Storyboard
If you look at the particular states we just defined in our ControlTemplate:
To make this clearer, here’s a screen shot of our skin in these states:
Now, on to the CheckStates:
1: <!-- CheckStates StateGroup-->
2: <vsm:VisualStateGroup x:Name="CheckStates">
3:
4: <!-- Unchecked State -->
5: <vsm:VisualState x:Name="Unchecked"/>
6:
7: <!-- Checked State -->
8: <vsm:VisualState x:Name="Checked">
9: <Storyboard>
10: <DoubleAnimation
11: Storyboard.TargetName="Checkmark"
12: Storyboard.TargetProperty="Opacity"
13: Duration="0" To="1"/>
14: </Storyboard>
15: </vsm:VisualState>
16:
17: <!-- Indeterminate State -->
18: <vsm:VisualState x:Name="Indeterminate">
19: <Storyboard>
20: <DoubleAnimation
21: Storyboard.TargetName="IndeterminateRect"
22: Storyboard.TargetProperty="Opacity"
23: Duration="0" To="1"/>
24: </Storyboard>
25: </vsm:VisualState>
26:
27: </vsm:VisualStateGroup>
We populate the CheckStates state group with three VisualStates:
Here’s a screen shot of the CheckBox in the different Check states.
Sweet!
So, we’ve created all the VisualStates for our CheckBox. What else do we need to do?
Well, today, the control code is responsible for initiating state changes using VisualStateManager. (You’ll learn about how the control code does this next time.) In the ControlTemplate, then, you don’t need to do anything else to have the VisualStates be detected & used.
To see our newly skinned CheckBox in action, run the app here.
Adding VisualTransitions
When you ran the CheckBox viewer application, you probably noticed that all the VisualStates “snapped” into place. That makes for a clunky user experience.
What we want is for each visual change to happen gradually. In Silverlight, you accomplish this by adding VisualTransitions to the different VisualStateGroups.
Adding a Default VisualTransition for a StateGroup
Let’s say we want all the states transition in CommonStates to take .5 seconds and all the state transitions in CheckStates to take .2 seconds. You get this effect by including a default VisualTransition in each state group.
1: <!-- VisualStateManager-->
2: <vsm:VisualStateManager.VisualStateGroups>
3:
4: <!-- CommonStates StateGroup-->
5: <vsm:VisualStateGroup x:Name="CommonStates">
6:
7: <!-- CommonStates Transitions-->
8: <vsm:VisualStateGroup.Transitions>
9: <vsm:VisualTransition GeneratedDuration="0:0:.5" />
10: </vsm:VisualStateGroup.Transitions>
11:
12: ...
13:
14: </vsm:VisualStateGroup>
15:
16: <!-- CheckStates StateGroup-->
17: <vsm:VisualStateGroup x:Name="CheckStates">
18:
19: <!-- CheckStates Transitions-->
20: <vsm:VisualStateGroup.Transitions>
21: <vsm:VisualTransition GeneratedDuration="0:0:.2" />
22: </vsm:VisualStateGroup.Transitions>
23:
24: ...
25:
26: </vsm:VisualStateGroup>
27:
28: </vsm:VisualStateManager.VisualStateGroups>
As shown in the above XAML, you create a default VisualTransition by adding a VisualTransition to the VisualStateGroups.Transitions property. VisualTransitions…
VisualStateManager, in turn, creates linear transition animations for all properties animated with…
… in the from and to state storyboards.
What do I mean by this? Let’s look at an example.
What happens in the transition from Normal to MouseOver? VisualStateManager detects that the MouseOver state storyboard animates the Glow element’s Opacity property, but the Normal state does not. It creates a linear DoubleAnimation for the Glow element’s Opacity going from 0 (the value in the Normal state) to 1 (the value in the MouseOver state).
VisualStateManager does this for each state transition: it examines the properties animated in the initial and final state and creates the appropriate transition between the two values. The result is a control look with gradual transitions created with minimal XAML.
See it for yourself: run our viewer app again.
Creating VisualTransitions for Specific State Changes
Adding a default VisualTransition helped the feel of our CheckBox a lot. But the transitions are still a bit awkward for some of the state changes.
For instance, you may want the visuals to snap when you go from the MouseOver to the Pressed state. This would help the “click” to feel more for immediate to the user. You do this by adding a transition and specifying the from and to state:
1: <!-- CommonStates Transitions-->
2: <vsm:VisualStateGroup.Transitions>
3: <vsm:VisualTransition GeneratedDuration="0:0:.5" />
4: <vsm:VisualTransition GeneratedDuration="0:0:0.8" To="MouseOver"/>
5: <vsm:VisualTransition GeneratedDuration="0:0:0.2" From="Pressed"/>
6: <vsm:VisualTransition GeneratedDuration="0" From="MouseOver" To="Pressed"/>
7: </vsm:VisualStateGroup.Transitions>
As you can see, VisualTransitions also have…
By inspecting these properties, VisualStateManager chooses the most specific transition from the VisualStateGroup.Transitions for a particular state change.
What happens when our CheckBox goes from MouseOver to Pressed? VisualStateManager looks first for a From=”MouseOver”/To=”Pressed transition. If it doesn’t find it, VSM then searches for a To=”Pressed” transition, and then, if necessary, a From=”MouseOver” transition. Finally, if it still hasn’t found a transition, VSM will use the default transition for that state group. If there was no default transition defined, it will use a zero length duration.
By giving specific VisualTransition for certain state changes, our CheckBox now looks and feels exactly the way we want!
Give the final app a try here. You can also grab the final XAML for the CheckBox skin as well as the full app sample code.
Next time
Okay, so that’s the basics of how to use VisualStateManager to reskin an existing control.
Next time, you’ll see how to build up a custom control from scratch that uses the Parts & States Model. You’ll also learn about how to use Parts in the template and how to add more complex transitions.
In Silverlight 2, we’ve added significant new support for managing states and transitions inside of controls. To help explain the Parts & States Model, I’ve put together a 4 part post series that will show how to:
Playing a starring role in this content will be VisualStateManager, or as we lovingly call it, VSM. VSM can be used with both UserControls and custom controls… but in this series, we’ll concentrate on its usage with the latter.
Today’s post introduces the Parts & States model at a conceptual level.
(Note: these posts assume that you have a basic understanding of UserControls, custom controls & control templates. If you’re just starting out, check out my Mix08 controls session or my TechEd controls overview session.)
Let’s get started!
Note: this tutorial has been updated for Silverlight 2 RTW.
(Series Link: Part 1, Part 2, Part 3, Part 4)
Motivation for the Parts & States Model
Custom controls are a type of Silverlight controls that have a strict separation between the control logic & control visuals. This is great for scenarios where you want to customize the visuals without affecting the logic, and vis versa.
While this strict separation has many benefits, it is often challenging for designers to know what elements in the template the control needs. What is missing is an explicit control contract.
If the control author provides an explicit control contract, the designer has a bill of materials for the control template. This enables easier skinning of controls.
Conceptually: Parts & States Model
The Parts & States model is a way of providing that control contract.
It is the recommended way to structure your Silverlight 2 controls. However, this pattern is not enforced by the runtime. You are free to build functioning controls that do not use the Parts & States Model.
That being said, not only do we do think the Parts & States Model is a good model - it is the model that Expression Blend supports. Therefore, if you want your control to be skinnable in Blend, you should build your control using the Parts & States paradigm.
At the highest level, there are four main concepts in the Parts & States Model:
Parts
Parts are named elements inside of a control template. The control logic expects these parts to appear in the template because it needs to manipulate them in some way.
In the above slider example, there are 4 parts. Each will be programmatically accessed by the control code. When the UpRepeatButton is pressed, the control code moves the Thumb along the Track to the right. When the DownRepeatButton is pressed, the control code moves the Thumb in the opposite direction.
Not all controls need to programmatically manipulate elements in this way. Such controls (e.g. Button) will not have any Parts in their control contract.
States
Visual states represent the way the control looks in a particular logical state.
For instance, the Button above has a light background when in the MouseOver state, and a dark background when in the Pressed state.
Transitions
Visual transitions represent the way the control looks as it transitions from one visual state to another.
Above, Button’s background gradually fades from a light color to a darker color as it transitions from the MouseOver to the Pressed state.
StateGroups
StateGroup are comprised of mutually exclusive states. State group themselves are orthogonal, meaning that a control can be in 2 different states as long as each of those states are in a different state group.
In the above CheckBox example, there are two state groups: CommonStates and CheckStates. A CheckBox can be in the MouseOver state and the Indeterminate state (for instance) because each of those states are members of a different state groups. On the other hand, it’s not possible for a CheckBox to be in the Normal and MouseOver state at the same time because they are two states in the same state group.
StateGroups are a new concept that we introduced in Beta2. They help reduce the “state explosion” that we saw in the Beta1 model. CheckBox has 7 states in Beta2 (plus 2 focus states). In Beta1, it had (AGH!) 12 states (focus was manipulates as a part and not a state).
Initiating State Changes
A state change starts when a control detects that, logically, it has changed state. It then initiates a visual state change, causing the appropriate visual transition and then visual state to be shown.
In the above example, for instance, a control detects a MouseEnter event. It then initiates a visual state change. The control’s visuals first show the appropriate transition and then rest in the MouseOver visual state.
Next Time
Okay, that’s the Parts & States Model at the conceptual level. In Part 2 of this series, we’ll walk through how to skin a CheckBox using the Parts & States Model.
Silverlight Beta2 is officially out in the wild!
All the required download links to get started are here:
Also, for the feature areas I drive, here are the major Beta2 updates:
Networking
Control Model Customization