Introduction
In this article, I want to illustrate an interesting aspect of Silverlight visual states and how they can be used in controls. You can read my blog here: Cellbi Blog.
It is not a secret that one of the key features in Silverlight controls is an ability to declare different states for a control and specify transitions between them.
Each transition between states can take time for animation, and often, it is necessary to know when a transition is really finished. Let us suppose that we've spent a couple of hours and prepared a simple button in Blend. Something that looks like the following picture ;-)
The button above consists of three parts: LayoutRoot
(a grid with width and height bound to the "Width
" and "Height
properties of the control), Host
(a button background with color property bound to the "Background
" property of the control), and Content
(a green arrow).
Visual States in XAML
Here is the result of such efforts in xaml code, which specifies simple style for the button.
<vsm:VisualStateManager.VisualStateGroups>
<vsm:VisualStateGroup x:Name="CommonStates">
<vsm:VisualStateGroup.Transitions>
<vsm:VisualTransition GeneratedDuration="00:00:00.4000000" To="MouseOver"/>
<vsm:VisualTransition GeneratedDuration="00:00:00.4000000" To="Play"/>
<vsm:VisualTransition GeneratedDuration="00:00:00.4000000"/>
</vsm:VisualStateGroup.Transitions>
<vsm:VisualState x:Name="Normal">
<Storyboard/>
</vsm:VisualState>
<vsm:VisualState x:Name="MouseOver">
<Storyboard>
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00"
Duration="00:00:00.4000000"
Storyboard.TargetName="Host"
Storyboard.TargetProperty="(UIElement.Opacity)">
<SplineDoubleKeyFrame KeyTime="00:00:00" Value="1"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</vsm:VisualState>
<vsm:VisualState x:Name="Play">
<Storyboard>
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00"
Duration="00:00:00.4000000"
Storyboard.TargetName="Host"
Storyboard.TargetProperty="(UIElement.Opacity)">
<SplineDoubleKeyFrame KeyTime="00:00:00" Value="0"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</vsm:VisualState>
</vsm:VisualStateGroup>
</vsm:VisualStateManager.VisualStateGroups>
Now, we need to add behaviour logic to this button. It should have two methods: Start
and Stop
. When the mouse moves over the button, the host should change its opacity. When the Stop
method is called or the button is simply clicked, the content should start rotation - the play state of the button.
Working with Visual States from C# Code
Ffrom the point of implementation, this can be done in the following way. As you can see from the picture, the button has three states: "Normal
", "MouseOver
" and "Play
". When the mouse pointer is moved over the control, we should start transition to the MouseOver
state, and when the button is clicked, we should start transition to the Play
state, and only after that, we start the rotation of the content.
Please note: "only after that" start the rotation of the content ;) The problem is that transitions between states can take time. And, if you start rotation of the content immediately after the button is clicked - it will occur in parallel mode with the transition, and trust me, it will look really ugly. So, to avoid this, we definitely should start the rotation after the transition is finished.
Silverlight has an easy way to do exactly this. We can use the visual state manager and visual states. The XAML code contains a VisualStateManager
which declares VisualStates and Transitions. So, to implement our task, we simply need to get access to the VisualStateGroup
s in the OnApplyTemplate
method override and handle several events. Let's take a look how the code would look like:
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
FrameworkElement root =
GetTemplateChild("LayoutRoot") as FrameworkElement;
.........
commonStatesGroup = GetVisualStateGroup(root, "CommonStates");
HookVisualStateGroupEvents(commonStatesGroup);
}
VisualStateGroup GetVisualStateGroup(FrameworkElement root, string groupName)
{
if (root == null)
return null;
IList groups = VisualStateManager.GetVisualStateGroups(root);
if (groups == null)
return null;
foreach (VisualStateGroup gr in groups)
if (gr != null && gr.Name == groupName)
return gr;
return null;
}
void HookVisualStateGroupEvents(VisualStateGroup group)
{
if (group == null)
return;
group.CurrentStateChanged += group_CurrentStateChanged;
}
void group_CurrentStateChanged(object sender,
VisualStateChangedEventArgs e)
{
if (e.NewState != null &&
e.NewState.Name == "Play")
OnStarted();
}
Now, let's see what is done in the above code. First, we get access to the "CommonStates
" visual group in the OnApplyTemplate
method, and then simply subscribe to the CurrentStateChanged
event. VisualStateChangedEventArgs
in the handler gives us only three properties: Control
, OldState
, and NewState
. However, this is all we need in this handler. ;-) When our event handler is called, we can say that the control was animated and we know which states the animation was done for. From this point, we know that the state of the control was changed and we can start content rotation.
I hope this sample will be useful.