WPF Toolkit: Visual State Manager Overview
BBefore we get into introducing this feature, let’s take a step back to the basics.
Fundamentally, an effective user interface minimizes the amount of effort a user has to
exert in order to achieve the required results. Consequently, an effective framework
is one that enables developers and designers to effortlessly create such rich user interfaces.
Having said that, we’ve introduced yet another addition to allow developers and designers better
realize such ambitions.
Introduction
In both Windows Presentation Foundation (WPF) and Silverlight we've established a significant new
addition to support the idea of control Parts and States. To help explain this model we've
introduced the Visual State Manager (VSM). This feature enables users to create a control contract
for the parts & states model; map the control logic to its respective visual states; and also enable
the user to create interchangeable skins. Additionally, this feature will also strengthen the
compatibility with our Silverlight counterpart. You can also find out more from Scott Guthrie’s
June 6th blog that talk about and help illustrate this feature.
Overview
The look and feel of a control will fundamentally determine how it visually responds to user interactions.
VSM utilizes the flexibility of control templates to enable customization of the look and feel of a control.
VSM introduces two basic concepts that you can take advantage of within control templates: "Visual States"
and "State Transitions". For example, a control like Button defines multiple visual states for itself -
"Normal", "MouseOver", "Pressed", "Disabled", "Focused", and "Unfocused".
When in template editing mode within Expression Blend, designers now have the ability to easily edit what the button looks like in each
particular state. Designers are also able to dictate how the button is affected as the button is switched
from one state to the other. In order to accommodate state changes, we’ve introduced the notion of transitions.
Transitions control the visual behavior between states. At runtime WPF will then dynamically run the appropriate
animation Storyboards to smoothly move the control from one state to another.
Design Details
In the below diagram, we can see that:
- The VisualStateManger.StateGroups attached property is where you define the state groups.
- The VisualStateGroup contain VisualStates, which in turn each contain a Storyboard
- The VisualStateGroup contains the Transitions
-
The specified Transitions may default a default transition, a specific transition to a state,
a specific transition between two states, etc.
- The control code initiates states changes by calling GoToState.
- The control contract is defined with [TemplateVisualState] and [TemplatePart]
Productivity
Custom controls are a type of WPF 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 vice versa.
This feature greatly helps achieve a separation of concerns. It also enables both developers and designers achieve
higher productivity by allowing each party to focus on what they do best.
What is nice about this model is that designers do not need to write code. Nor do they need to learn the logic model of controls.
This simplifies the learning curve for creating rich and interactive controls.
Tooling Capability
That being said, not only is the VSM feature an all-around enhancement to the WPF framework, it is a blueprint for a model that
is easily and effectively tool-able. This is the model that Expression Blend will support. Therefore, designers are now able
to easily create skin-able controls using Expression Blend by utilizing the Parts & States paradigm.
Scenarios
Make a Button have a Glow Animation on MouseOver
Steady state animations can be specified in the VisualState.Storyboard.
The steady state is the state that a target sits in when it is not transitioning between visual states.
<VisualStateGroup x:Name="CommonStateGroup">
<VisualState x:Name="MouseOverState">
<Storyboard>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="Halo"
Storyboard.TargetProperty="(UIElement.Opacity)"
BeginTime="00:00:00" RepeatBehavior="Forever">
<SplineDoubleKeyFrame KeyTime="00:00:00.2" Value="0.008"/>
<SplineDoubleKeyFrame KeyTime="00:00:00.5" Value="0.009"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
...
</VisualStateGroup>
Default transitions between states
We can specify a default transition time that should be used for any state transition within a group.
<VisualStateGroup x:Name="CommonStateGroup">
...
<VisualStateGroup.Transitions>
<VisualTransition Duration="0:0:0.1"/>
</VisualStateGroup.Transitions>
</VisualStateGroup>
From/To transitions
We can specify the transition from one specified state to another.
<VisualStateGroup x:Name="CommonStateGroup">
...
<VisualStateGroup.Transitions>
...
<VisualTransition From="MouseOver" From="Pressed" Duration="0:0:0.1"/>
</VisualStateGroup.Transitions>
</VisualStateGroup>
Specifying a specific storyboard for a transition
We can specify a transition Storyboard whose animations will be used as basis for the transition animations.
<VisualStateGroup x:Name="CommonStateGroup">
...
<VisualStateGroup.Transitions>
...
<VisualTransition From="MouseOver" From="Pressed" Duration="0:0:0.1"/>
</VisualStateGroup.Transitions>
</VisualStateGroup>
API
VisualStateManager
It contains the logic of how to go to a different visual state. Also contains StateGroups and Current attached properties.
public class VisualStateManager : DependencyObject
{
// public static bool GoToState()
// ------------------------------
// 0. If VisualStateManager.Current is NOT null for the passed in element, call
// VisualStateManager.Current.GoToStateCore();
// 1. OTHERWISE,
// 1a. stop the existing state’s storyboard
// 1b. If useTransitions is true, finds/generates the appropriate transition storyboard
// by examining the VisualStateGroup.Transitions
// 1c. Starts new state’s storyboard
//
// Returns true if state with passed in stateName exists, false otherwise.
public static bool GoToState(FrameworkElement element, string stateName,
bool useTransition);
// protected static void GoToState()
// ------------------------------
// Used to override the default GoToState() behavior
protected virtual bool GoToStateCore(FrameworkElement element, string stateName,
VisualState state, bool useTransition);
// VisualStateGroups Attached property
public static readonly DependencyProperty StateGroupsProperty;
public static VisualStateGroupCollection GetStateGroups(DependencyObject obj);
public static void SetStateGroups(DependencyObject obj,
VisualStateGroupCollection value);
// Current attached property
public static readonly DependencyProperty CustomVisualStateManagerProperty;
public static VisualStateManager GetCustomVisualStateManager(DependencyObject obj);
public static void SetCustomVisualStateManager(DependencyObject obj,
VisualStateManager value);
VisualState
It contains the steady state Storyboard & reference to the associated state group.
[ContentProperty("Storyboard")]
public sealed class VisualState : DependencyObject
{
public static readonly DependencyProperty NameProperty;
public string Name { get; }
public Storyboard Storyboard { get; set; }
public VisualStateGroup StateGroup { get; }
}
VisualTransition
It contains the Storyboard & reference to the associated state group.
[ContentProperty("Storyboard")]
public sealed class VisualTransition : DependencyObject
{
public string To { get; set; }
public string From { get; set; }
public Storyboard Storyboard { get; set; }
public Duration Duration { get; set; }
}
VisualStateGroup
Contains the states for the group and the associated transitions.
[ContentProperty("States")]
public sealed class VisualStateGroup : DependencyObject
{
public VisualStateGroup();
public static readonly DependencyProperty NameProperty;
public string Name { get; }
public Collection<VisualState> States { get; }
public Collection<VisualTransition> Transitions { get; }
}
VisualStateGroupCollection
It contains the collection of visual state groups.
public class VisualStateGroupCollection : Collection<VisualStateGroup>
{ }
TemplateVisualStateAttribute
It is used to define what states the control is expecting the template to provide.
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public sealed class TemplateVisualStateAttribute : Attribute
{
public TemplateVisualStateAttribute();
public string Name { get; set; }
public string StateGroupName { get; set; }
}
Sample Code
In order to illustrate some of the scenarios for VSM, I will be referring to the sample Weather App written by John Gossman.
The source code can be downloaded from his blog by clicking here.
I’ve included a screen shot of this sample app below.
XAML Code for Visual Design
In this sample app the user is able to click the different weather buttons on the top left to transition from one visual to another.
<!-- VisualStateManager -->
<VisualStateManager.VisualStateGroups>
<!-- WeatherStates StateGroup-->
<VisualStateGroup x:Name="WeatherStates">
<!-- WeatherStates States-->
<VisualState x:Name="Sunny"/>
<VisualState x:Name="PartlyCloudy"
Storyboard="{StaticResourc PartlyCloudyStoryboard}"/>
<VisualState x:Name="Cloudy"
Storyboard="{StaticResource CloudyStoryboard}"/>
<VisualState x:Name="Rainy"
Storyboard="{StaticResource RainyStoryboard}"/>
<!-- WeatherStates Transitions-->
<VisualStateGroup.Transitions>
<!-- Sunny to PartlyCloudy Transition -->
<VisualTransition From="Sunny" To="PartlyCloudy" Duration="0:0:0.5">
<Storyboard>
...
</Storyboard>
</VisualTransition>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
In the code segment above, a new weather visual state group is defined within the Visual State Manager.
This state group then encompasses the various visual states of the weather control. In this case it defines the Sunny,
PartlyCloudy, Cloudy, and Rainy visual states. Most of these visual states define a static state Storyboard.
A visual transition is also defined for the state transition from the Sunny to PartlyCloudy state.
The visual transition also defines the duration of the animation.
C# Attributes for Logical Development
In the code the developer must use attributes to define the various visual states for the weather control.
These attributes are declared for the custom weather control.
[TemplateVisualState(Name = "Sunny", StateGroup="WeatherStates")]
[TemplateVisualState(Name = "PartlyCloudy", StateGroup = "WeatherStates")]
[TemplateVisualState(Name = "Cloudy", StateGroup = "WeatherStates")]
[TemplateVisualState(Name = "Rainy", StateGroup = "WeatherStates")]
C# Code for Logical Development
In the code the developer might want to take care of the initial visual state if visual states are defined in a template.
Therefore, we would need to over the OnApplyTemplate method to handle this. The GoToState method is then implemented for
the custom weather control to handle the state changes.
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
GoToState(false);
}
private void GoToState(bool useTransitions)
{
// Go to states in WeatherStates state group
if (Condition == Condition.PartlyCloudy)
{
VisualStateManager.GoToState(this, "PartlyCloudy", useTransitions);
}
else if (Condition == Condition.Sunny)
{
VisualStateManager.GoToState(this, "Sunny", useTransitions);
}
else if (Condition == Condition.Cloudy)
{
VisualStateManager.GoToState(this, "Cloudy", useTransitions);
}
else
{
VisualStateManager.GoToState(this, "Rainy", useTransitions);
}
}
Where to Find VSM
You can pick up the new VSM feature from the WPF Toolkit. Coming soon to CodePlex,
look for it starting October 28th!