Click here to Skip to main content
Click here to Skip to main content

Control to Support Custom Animations using VisualStateManager

, 5 May 2011 CPOL
Rate this:
Please Sign up or sign in to vote.
Implements a control to support changing colors for Silverlight Shapes using VisualStateManager

Introduction

In Silverlight, animations can be defined in XAML using the VisualStateManager. However, there are some significant disadvantages to this technology, especially when attempting to create controls that can be reused. Also, it is best to attempt to keep the definition of the view either in code or XAML. Having it in both places makes maintenance harder. Ideally as much as possible should be done in XAML, which is the whole point of creating XAML instead of sticking to the technology of WinForms.

Background

The group I work for now has several people responsible for the design of the user interface. They have made extensive use buttons that consist only of an icon. These icons have behaviors associated with mouse over and pressed states. The initial icons I had to deal with were defined with a single path and there was only a single color change. This meant that the fill of the path had to change depending on the VisualStateManager CommonStates MouseOver and Pressed. The way that this had been solved originally was to create a control template for each different icon that served as a button. This is not at all desirable since it means that many changes will require changing all or many the buttons. Then there could be both Buttons and ToggleButtons using the same icon. My initial concept was a ControlTemplate that took a Content which was a string that was the path value for a Path control, and the Fill was defined as the Foreground:

<Style x:Key="PathButton" TargetType="Button">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Grid Background="Transparent">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="Pressed">
<Storyboard>
<ColorAnimation Duration="0" To="{StaticResource AccentColor}"
Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)"
Storyboard.TargetName="ButtonPath" />
</Storyboard>
</VisualState>
<VisualState x:Name="MouseOver">
</VisualState>
<VisualState x:Name="Disabled">
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Path Name="ButtonPath" HorizontalAlignment="Center" VerticalAlignment="Center"
Fill="{TemplateBinding Foreground}" Data="{TemplateBinding Content}" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>

This initially looked like it was right solution, but it did not handle all the icon buttons.

The first recognized issue was when there was more than a single path. To handle this situation, one of my co-workers created a control that basically managed all the content in a ControlTemplate for a Button. Not only did it handle colors for multiple paths, but it was possible to state which paths would be affected, and also apply render transforms. It seemed to handle all the button behaviors that we needed.

I then ran into a case where there were multiple color changes, and the ControlTemplate did not handle multiple colors. I could have probably extended the existing ControlTemplate to handle multiple colors, but I did not like the concept. Basically we were providing another implementation of the VisualStateManager. The VisualStateManager is a standard in Silverlight the Silverlight developers understand. It also provides a very obvious definition of the defined behavior. Unfortunately, Silverlight does not let you just arbitrarily create DependencyProperties for a control, not even the ContentlPresenter. It seemed to me that it would be best to create a ContentControl that contained the properties that I needed to create the desired behavior.

I therefore created a control that contained two new DependencyProperties: Color1 and Color2. When the control is initialized, or the content changes, then the controls in the Content are scanned using VisualTreeHelper.GetChild method. Each control that is to be changed using the VisualStateManager has a DependencyProperty set that contains the string that will indicate that the control (Shape) fill color will be controlled by either Color1 or Color2 DependencyProperty, and will be put in the List associated with that Color property.

The Color1 and Color2 DependencyProperties each have a change event handler that will update all the controls in their associated list so that the fill is set to a SolidColorBrush with the new color.

Using the Code

For anyone familiar with using the VisualStateManager, the use of this control is actually very simple. Unfortunately, a ControlTemplate has to be created to contain the VisualStateManager XAML. In this case, I am using a control for a Button:

<Style x:Key="TwoColorChangeButton" TargetType="Button">
<Setter Property="VerticalContentAlignment" Value="Top" />
<Setter Property="BorderThickness" Value="0" />
<Setter Property="TabNavigation" Value="Local" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Grid Background="{TemplateBinding Background}">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualStateGroup.Transitions>
<VisualTransition GeneratedDuration="0:0:0.1">
<VisualTransition.GeneratedEasingFunction>
<QuadraticEase EasingMode="EaseOut" />
</VisualTransition.GeneratedEasingFunction>
</VisualTransition>
</VisualStateGroup.Transitions>
<VisualState x:Name="Normal" />
<VisualState x:Name="MouseOver">
<Storyboard>
<ColorAnimation Duration="0" To="LightGreen" Storyboard.TargetProperty="Color1"
    Storyboard.TargetName="ContentControl" />
<ColorAnimation Duration="0" To="Black" Storyboard.TargetProperty="Color2"
    Storyboard.TargetName="ContentControl" />
</Storyboard>
</VisualState>
<VisualState x:Name="Pressed">
<Storyboard>
<ColorAnimation Duration="0" To="Red" Storyboard.TargetProperty="Color1"
    Storyboard.TargetName="ContentControl" />
<ColorAnimation Duration="0" To="Green" Storyboard.TargetProperty="Color2"
    Storyboard.TargetName="ContentControl" />
</Storyboard>
</VisualState>
<VisualState x:Name="Disabled">
<Storyboard>
<ColorAnimation Duration="0" To="LightGray" 
 Storyboard.TargetProperty="Color1" Storyboard.TargetName="ContentControl" />
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<views:ColorAdapterControl x:Name="ContentControl" 
     Color2="White" Color1="Black" Content="{TemplateBinding Content}" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>

You could directly code the paths within the content, but since this was part of a larger project I defined the paths in the Grid.Resources:

<ControlTemplate x:Key="ButtonContent_Plus2" TargetType="ContentControl">
<Canvas Width="12.1092" Height="12.1096">
<Path Width="12.1092" Height="12.1096" Canvas.Left="0.390177" Canvas.Top="0.391614" 
  Stretch="Fill" views:ColorAdapterExt.FillColorSelector="Color1" 
  Data="F1 M 7.25134,0.446381C 10.5649,0.892731 12.8903,3.94067 12.4446,
        7.25317C 11.9994,10.5666 8.95322,12.8927 5.63794,12.4463C 2.32419,
        12.0019 0,8.95447 0.444702,5.64102C 0.890839,2.32654 3.93753,
        0.000457764 7.25134,0.446381" />
<Path Width="5.63623" Height="5.6366" Canvas.Left="3.63184" 
   Canvas.Top="3.6283" Stretch="Fill" views:ColorAdapterExt.FillColorSelector="Color2"
   Data="F1 M 6.99142,9.26489L 5.90842,9.26489L 5.90842,6.98758L 3.63184,
         6.98758L 3.63184,5.90463L 5.90842,5.90463L 5.90842,3.6283L 6.99142,
         3.6283L 6.99142,5.90463L 9.26807,5.90463L 9.26807,
         6.98758L 6.99142,6.98758L 6.99142,9.26489 Z " />
</Canvas>
</ControlTemplate>

Then in the Silverlight page, all I need is the following XAML to create my button:

<Button x:Name="SelectButton"
  HorizontalAlignment="Center" VerticalAlignment="Center"
  Background="Transparent" Style="{StaticResource TwoColorChangeButton}">
<ContentControl Template="{StaticResource ButtonContent_Plus2}" />
<Button.RenderTransform>
<ScaleTransform ScaleX="4" ScaleY="4" />
</Button.RenderTransform>
</Button>

Points of Interest

I only used two colors and only changed the Fill color of paths. The code is set up so that any Shape can be used within the content. This technique can be used for any number of values on many different DependencyProperties. One example may be controlling the height of a row or width of a column in a Grid. These properties are of the type GridLength, so cannot be controlled by an animation, but with an Adapter like this, it is possible.

History

  • 5th May, 2011: Initial post

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Clifford Nelson
Software Developer (Senior) ETeam/Delloitte
United States United States
Has been working as a C# developer on contract for the last several years, including 3 years at Microsoft. Previously worked with Visual Basic and Microsoft Access VBA, and have developed code for Word, Excel and Outlook. Started working with WPF in 2007 when part of the Microsoft WPF team. For the last three years has been working primarily as a senior WPF/C# and Silverlight/C# developer. Currently working as WPF developer with NBC Universal in Universal City, CA

Comments and Discussions

 
-- There are no messages in this forum --
| Advertise | Privacy | Terms of Use | Mobile
Web03 | 2.8.141216.1 | Last Updated 5 May 2011
Article Copyright 2011 by Clifford Nelson
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid