Click here to Skip to main content
15,881,173 members
Articles / Web Development / HTML

Control to Support Custom Animations using VisualStateManager

Rate me:
Please Sign up or sign in to vote.
4.89/5 (9 votes)
5 May 2011CPOL4 min read 23K   408   14  
Implements a control to support changing colors for Silverlight Shapes using VisualStateManager

Introduction

In Silverlight, animations can be defined in XAML using the <font face="Courier New">VisualStateManager</font>. 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 <font face="Courier New">VisualStateManager</font> <font face="Courier New">CommonStates</font> <font face="Courier New">MouseOver</font> and <font face="Courier New">Pressed</font>. 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 Button and ToggleButton controls using the same icon. My initial concept was a <font face="Courier New">ControlTemplate</font> that took a <font face="Courier New">Content</font> which was a <font face="Courier New">string </font>that was the path value for a <font face="Courier New">Path</font> control, and the <font face="Courier New">Fill</font> was defined as the <font face="Courier New">Foreground</font>:

<Style x:Key="PathButton" TargetType="Button">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="Button">
                <Grid Background="Transparent">
                    <Path Name="ButtonPath"
                          HorizontalAlignment="Center"
                          VerticalAlignment="Center"
                          Fill="{TemplateBinding Foreground}"
                          Data="{TemplateBinding Content}" />
                    <VisualStateManager.VisualStateGroups>
                        <VisualStateGroup x:Name="CommonStates">
                            <VisualState x:Name="Normal" />
                            <VisualState x:Name="Pressed">
                                <Storyboard>
                                    <ColorAnimation Duration="0"
                                                    Storyboard.TargetName="ButtonPath"
                                                    Storyboard.TargetProperty="(Shape.Fill)
                            .(SolidColorBrush.Color)"
                                                    To="{StaticResource AccentColor}" />
                                </Storyboard>
                            </VisualState>
                            <VisualState x:Name="MouseOver" />
                            <VisualState x:Name="Disabled" />
                        </VisualStateGroup>
                    </VisualStateManager.VisualStateGroups>
                </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 <font face="Courier New">ControlTemplate</font> for a <font face="Courier New">Button</font>. 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 <font face="Courier New">ControlTemplate</font> did not handle multiple colors. I could have probably extended the existing <font face="Courier New">ControlTemplate</font> to handle multiple colors, but I did not like the concept. Basically we were providing another implementation of the <font face="Courier New">VisualStateManager</font>. The <font face="Courier New">VisualStateManager</font> 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 <font face="Courier New">DependencyProperties</font> for a control, not even the <font face="Courier New">ContentlPresenter</font>. It seemed to me that it would be best to create a <font face="Courier New">ContentControl</font> that contained the properties that I needed to create the desired behavior.

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

The <font face="Courier New">Color1 </font>and <font face="Courier New">Color2 </font><font face="Courier New">DependencyProperties</font> each have a change event handler that will update all the controls in their associated list so that the fill is set to a <font face="Courier New">SolidColorBrush</font> with the new color.

Using the Code

For anyone familiar with using the <font face="Courier New">VisualStateManager</font>, the use of this control is actually very simple. Unfortunately, a <font face="Courier New">ControlTemplate</font> has to be created to contain the <font face="Courier New">VisualStateManager</font> XAML. In this case, I am using a control for a <font face="Courier New">Button</font>:

<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><span style="margin: 0px; line-height: 107%; font-family: "Segoe UI", sans-serif; font-size: 10.5pt">Buttons </span>

You could directly code the paths within the content, but since this was part of a larger project I defined the paths in the <font face="Courier New">Grid.Resources</font>:

<ControlTemplate x:Key="ButtonContent_Plus2" T
                 argetType="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"
            Style="{StaticResource TwoColorChangeButton}"
            Background="Transparent">
        <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 <font face="Courier New">Fill</font> color of paths. The code is set up so that any <font face="Courier New">Shape</font> can be used within the content. This technique can be used for any number of values on many different <font face="Courier New">DependencyProperties</font>. One example may be controlling the height of a row or width of a column in a <font face="Courier New">Grid</font>. These properties are of the type <font face="Courier New">GridLength</font>, 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)


Written By
Software Developer (Senior) Clifford Nelson Consulting
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 eight years has been working primarily as a senior WPF/C# and Silverlight/C# developer. Currently working as WPF developer with BioNano Genomics in San Diego, CA redesigning their UI for their camera system. he can be reached at qck1@hotmail.com.

Comments and Discussions

 
-- There are no messages in this forum --