Visual States, from the coined term, signifies the current state of display. In WPF, we can refer visual states to controls or even windows. For example a button can have a pressed state or a hover state, the same thing can be applied on the entire window whether we can show that what is being displayed is the login state (with the controls and login button) or in the main user workspace state. Additionally in WPF, we can define “Visual Transitions”, same as Microsoft Powerpoint’s slides transitioning, whereby entering from one slide (or visual state) to another may have a fading or whatever transition effects.
Visual States was introduced in the Silverlight first, and then it was introduced into WPF Toolkit continue, Microsoft Expression Blend 2.5 and later supports Visual State Manager to develop the style/template. WPF 4 introduces Visual State Manager that user can set visual states in the control template and manage to transit the states based on the different conditions. Visual State Manager class seems the trigger in the template, but it can organize the properties of the states rationally and transit the different visual states easily through VisualTransition class.
Declaring Visual States in XAML
Our visual states are maintained by a VisualStateManager. We need to define a group of visual states first. We can place the following tags inside the hosting element that will be performing the visual state transitions:
1
2
3
4
5
6
7
8
9
10
11
12
|
<
UserControl
x:Class
=
"Desktop.Shell.MainView"
xmlns:vw
=
"clr-namespace:Desktop.Shell.Views"
>
<
Grid
>
<
VisualStateManager.VisualStateGroups
>
<
VisualStateGroup
x:Name
=
"MainVisualStateGroup"
>
<
VisualState
x:Name
=
"LoginState"
/>
</
VisualStateGroup
>
</
VisualStateManager.VisualStateGroups
>
<
vw:LoginView
/>
<
vw:WorkspaceView
Opacity
=
"0"
/>
</
Grid
>
</
UserControl
>
|
A visual state group will contain related visual states where the visual transitions will be able to use in moving from one state to another. For more info see: VisualStateGroup.
From the above example, we are creating a default visual state which is a login state. This state is whatever is the current display: visibility, locations and styles of the controls. The current state of display is that our login view (our login user control) is visible while the workspace view is not as noted by Opacity=0.
In here I chose Opacity since we can control how faded our view can be as well as visibility. Due to opacity value, our transition can score from 0 incrementing by 0.1 until it reaches full visibility to 1.
Next we are going to create our second visual state and with fading transition of course:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
<
UserControl
x:Class
=
"Desktop.Shell.MainView"
xmlns:vw
=
"clr-namespace:Desktop.Shell.Views"
>
<
Grid
x:Name
=
"mainGrid"
>
<
VisualStateManager.VisualStateGroups
>
<
VisualStateGroup
x:Name
=
"MainVisualStateGroup"
>
<
VisualState
x:Name
=
"LoginState"
/>
<
VisualState
x:Name
=
"WorkspaceState"
>
<
StoryBoard
>
<
DoubleAnimationUsingKeyFrames
Storyboard.TargetProperty
=
"Opacity"
Storyboard.TargetName
=
"loginView"
>
<
EasingDoubleKeyFrame
KeyTime
=
"0"
Value
=
"1"
/>
<
EasingDoubleKeyFrame
KeyTime
=
"0:0:0.2"
Value
=
"0"
/>
</
DoubleAnimationUsingKeyFrames
>
<
DoubleAnimationUsingKeyFrames
Storyboard.TargetProperty
=
"Opacity"
Storyboard.TargetName
=
"workspaceView"
>
<
EasingDoubleKeyFrame
KeyTime
=
"0"
Value
=
"0"
/>
<
EasingDoubleKeyFrame
KeyTime
=
"0:0:0.2"
Value
=
"1"
/>
</
DoubleAnimationUsingKeyFrames
>
</
StoryBoard
>
</
VisualState
>
</
VisualStateGroup
>
</
VisualStateManager.VisualStateGroups
>
<
vw:LoginView
x:Name
=
"loginView"
/>
<
vw:WorkspaceView
x:Name
=
"workspaceView"
Opacity
=
"0"
/>
<
Grid
>
</
UserControl
>
|
From the above example, we added a new visual state named Workspace state. Inside we declared a storyboard animation with two double animation using key frames. We use double since the type of the value of the target property which is Opacity is double.
The first animation targets our login view and its opacity property. At key time 0, we declared the value as 1 which is the default opacity of this view. At key time 0.2 seconds, we declared value of 0. This means that this animation will transition the opacity value from 1 to 0 in 0.2 seconds making a fading out effect.
The second animation targets our workspace view and its opacity property. Since we are going to make this view visible, at key time 0, we declared the value as 0 which is the invisibility of this view. At key time 0.2 seconds, we declared value of 1. This means that this animation will transition the opacity value from 0 to 1 in 0.2 seconds making a fading in effect.
Changing from one state to another
Now that we have our declared states and animation. The only thing required is to let our visual state manager execute state transitions to move to another state. In code we only need to call GoToState method of VisualStateManager:
1
|
VisualStateManager.GoToState(mainGrid,
"WorkspaceState"
,
true
);
|
Parameters are:
- The Control containing the visual state. For this case its the grid.
- The name of the state to transition.
- Flag whether to use transitions (our declared storyboard) or not.
Since this is currently called in C#, we can further enhance to coincide with MVVM pattern by creating a dependency attached property:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
public
class
StateManager : DependencyObject
{
public
static
string
GetVisualStateProperty(DependencyObject obj)
{
return
(
string
)obj.GetValue(VisualStatePropertyProperty);
}
public
static
void
SetVisualStateProperty(DependencyObject obj,
string
value)
{
obj.SetValue(VisualStatePropertyProperty, value);
}
public
static
readonly
DependencyProperty VisualStatePropertyProperty =
DependencyProperty.RegisterAttached(
"VisualStateProperty"
,
typeof
(
string
),
typeof
(StateManager),
new
PropertyMetadata((dependencyObject, args) =>
{
var frameworkElement = dependencyObject
as
FrameworkElement;
if
(frameworkElement ==
null
)
return
;
VisualStateManager.GoToState(frameworkElement, (
string
)args.NewValue,
true
);
}));
}
|
This way we can create a class where we can just set the State property:
1
|
MainViewModel.ViewState =
"workspaceState"
|
and bind that property to the view using this attached property method:
1
|
StateManager.VisualStateProperty = "{Binding Path=ViewState}"
|
And that’s it. I enjoyed visual states and using Microsoft Expression Blend’s powerful animation wysiwyg editor, we can easily create custom animations.
Enjoy!