Welcome to WindowsClient.net | Sign in | Join

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]
Toolkit View State Manager Screenshot

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.

VisualStateManager Sample Screenshot

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!


Featured Item