Visual State Manager tips for design and authoring
As you probably know, Silverlight and WPF have a runtime piece called the Visual State Manager (or VSM for short). As I’ll describe in this post, VSM and the Expression Blend tooling support for VSM lend a nice clean mental model to the business of visual states and visual state changes for both custom controls and UserControls.
Although chronologically the story begins with the control author, I’ll talk about that aspect later in this post simply because there are more people in the world concerned with the visual stuff than with the logical stuff. The visual aspect begins in Blend, with a set of already-defined visual states organized into groups in the States panel.
You can identify three stages in the design of visual states and transitions. First, the static stage. Here you make each visual state look the way you want it to, and you do so ideally without any thought of transitions. You select a state in the States panel and you change object properties. And speaking of the States panel, this is probably a good time to introduce the idea of state groups.
Visual states are grouped in such a way that a) the states within a state group are mutually exclusive of one another and b) the states contained in any group are independent of the states contained in any other group. This means that one, and any one, state from every group can be applied at the same time without conflict. An example is a check box where the checked states are independent of, and orthogonal to, the mouse states. Changing an object’s property in more than one state within the same group is common practice. For example, you might change a Rectangle’s Fill to different colors in MouseOver, Pressed and Disabled. This works because only one state from the CommonStates state group is ever applied at a time. But changing an object’s property in more than one state group breaks the independent nature of the state groups and leads to conflicts where more than one state is trying to set the same object’s property at the same time. Blend will display a warning icon (with a tooltip containing details) next to any affected state group whenever this conflict situation is detected.
Each state group should contain a state that represents the default state for that group. CommonStates has ‘Normal’, CheckedStates has ‘Unchecked’, and so on. It is a good (and efficient) practice to set objects’ properties in Base such that no changes have to be made in any ‘default’ state. So, for example, you would hide a check box’s check glyph and focus rectangle in Base and then show them in Checked and Focused respectively.
So now you can click through the states to confirm that each looks correct. You can build and run, and test your states, and you might even stop at this stage if you’re happy that the control switches instantly from one state to another. But if instant state switches are not what you want then you can breathe life into your transitions in stage two, the transitions stage. First, add any transitions you want to see, then set transition durations and easing on them, still in the States panel. And again, in very many cases this will be enough for your scenario. What’s interesting for those designers who can stop at this point is that there was no need to open the Timeline and no need to be bothered with the attendant concepts of what a Storyboard is, etc. To keep unnecessary concepts and UI out of your face, by default Blend keeps the Timeline closed when you select a visual state or edit a transition duration. You can of course open it at any time with the Show Timeline button.
Still in the transitions stage, there may be times when you need a property’s value to change during the transition from StateA to StateB but, because of the way StateA and StateB are defined, the property either doesn’t change or doesn’t pass through the desired value. In this case you need to customize that transition. Select the transition and then use the Timeline as normal to define the animations that should take place during the transition.
The last stage is dynamic states. If, for example, you want a blue rectangle to subtly pulse while a control has focus then you need a steady-state animation. I also call them ‘in-state animations’ because the animation happens while you’re ‘in a state’. To do this, just select the state, open the Timeline, and go ahead and keyframe as usual, possibly also selecting the Storyboard and setting repeat behavior and auto reverse.
Now let’s move onto the topic of how states relate to control authoring. Before a designer can begin deciding what states and transitions look like, the control author must decide what states exist. As a control author, your job is not to think about visual states, but logical states. Forget what it looks like; what does it mean? You need to consider all the ways the end user (and possibly other factors such as invalid data) can interact with the control, and from that thinking build out a set of candidate states; and the states are logical at this point because they have no ‘look’. Now’s the time to think about whether your candidate states need factoring. Look for islands of states: closed graphs that don’t link to other states. There are two kinds: orthogonal and nested. Orthogonal islands should be put in their own state group. An example is that of CheckedStates and FocusedStates. There are no transitions between a CheckedStates state and a FocusedStates state, and a control is either checked or not, and focused or not, and there is no dependency between those two aspects. Islands that should be nested have the characteristics that there are no transitions between a StateGroupA state and a StateGroupB state, and the states in StateGroupB are only effective for one state in StateGroupA. For example if StateGroupA contains LoginPageHidden and LoginPageShown, and StateGroupB contains LoginNormal and LoginError, then it’s clear that StateGroupB is only effective during StateGroupA.LoginPageShown. In this case the two state groups are not orthogonal so you may choose not to use state groups. An alternative, and arguably cleaner, design would be to put StateGroupA at one level of hierarchy with StateGroupB defined on a nested control. Another point to bear in mind, related to naming states, is that each state name must be unique for a control type, even across state groups.
When a control initializes, it first has to get itself onto the state graph. This is important. If a control doesn’t do this then it is still in Base after initialization. Base is not a state; it merely represents the control with its local (or ‘base’) property values set, with no states applied. When the mouse pointer first moves over this control it will go to MouseOver but it will go there from Base, so the Normal -> MouseOver transition will not run the first time. This is a subtle bug that the consumer of your control cannot fix by defining Base -> MouseOver, because Base is not a state. So when you author your own templated control or UserControl, you should define a ‘default’ state in each state group. Have the control go to those ‘default’ states when it initializes, and do so with transitions suppressed so that it happens without delay. Once it’s on the state graph, the control is ready for state transitions to occur so now you can implement the event-handlers that trigger the transitions within the state graph.
I hope this post has been useful and has helped clarify some of the less obvious aspects of designing and authoring controls to work well with the Visual State Manager. If you want to see a walkthrough of some of the ideas presented here, you could try my Button styling video.