AspAlliance.com LogoASPAlliance: Articles, reviews, and samples for .NET Developers
URL:
http://aspalliance.com/articleViewer.aspx?aId=1857&pId=-1
Usage of VisualStateManager to Define User Interface in Silverlight 3
page
by Sergey Zwezdin
Feedback
Average Rating: This article has not yet been rated.
Views (Total / Last 10 Days): 39008/ 75

Introduction

A possibility to describe declaratively a user interface based on XAML has become the reason to think about the building of the user interfaces in a different way. This approach has allowed distribution work between a programmer and designer. It has defined a new branch of a user interfaces development industry.

As is known, user interfaces are based on the set of templates (ControlTemplate) for control elements. It is true for both WPF and Silverlight. Usually templates are a kind of layout, containing elements for a user interface definition. Triggers are usually used when there is a need to define some appearance changes depending on any condition. For these purposes a property or event is used to trace the moment of a control element state changing.

This approach is good in case of simple logic. However, it is easy to understand that logic of triggers will be really confused in cases where there are a multiple set of conditions or it is necessary to supervise some conditions simultaneously. In some cases creation, debugging and support for such applications can become a big hell for a developer or designer.

To resolve this conceptual problem there is a suggestion to have another look at user interfaces development. It is proposed to define a set of conditions based on representation of definition for a control element instead of triggers and behavioral representation. In this case every control element will be in some state, and appearance depends on the current state of a control element.

The definition of control elements appearance that is based on states is considered in the article where you can read about the convenient way to set visual representation of control elements.

The Concept

The developer usually does not think about a control element representation when it is underway. More important is to program logic of a control element. External representation is set later. As a general rule, a template (ControlTemplate) is formed to that end. However, any control element (even the simplest one) has a set of conditions that allows it to look differ. These conditions are named states. For example, an ordinary button can have several states - normal, pressed, hovered, etc. It is more logical when business logic of a control element informs about the current state, and the control element is in the process of representation changing. Work of VisualStateManager object is based on this approach.

VisualStateManager object allows determination of a state of a current control element. Usually the level of business logic does it within a control element. A control element internal event can be the reason (for example, press a button of a mouse or input text). It is necessary to attach VisualStateManager object to an internal control element within a template (ControlTemplate) to set representation. So, VisualStateManager object within a template will trace a current state automatically and execute transformations of user interface if there is need.

Anatomy of VisualStateManager

VisualStateManager object is a link between business logic of a control element and its external representation. To connect VisualStateManager to a template of a control element it is necessary to define an attached property of VisualStateGroups. Within this property it is necessary to describe all states and transitions, and change the display of a control element appearance depending on a state. It is necessary to do it within business logic of a control element to change these states actually. Usage of GoToState method helps to do it. The name of the state is passed as parameters of this method. The state is set as a usual text line. It is possible to define a unique set of states for each control. Also, this method contains the third parameter "useTransitions"  which indicates whether it is necessary to use "Transitions" going to this state.

Figure 1: VisualStateManager's parts

So, as a result of GoToState method call, a necessary template is applied in a template of a control element. Good news is that calls of GoToState method have already been built in standard control element (such as Button, ListBox, ListBoxItem, etc.). It means that it is possible to use advantages of VisualStateManager for existing standard controls right now.

To add VisualStateManager object to a template of a control element it is necessary to use attached property VisualStateGroups. In this case it is possible to set some groups to visualize states and define sets of states for each group. Each visual state contains Storybaord which allows us to set animation jumping to the given state. If there is no need in animation during such jumps, then objects with animation based on fixed TimeLine should be used. So, a control element template structure could be presented as the following.

Figure 2: Structure of template at use VisualStateManager

Definition of states on the basis of VisualStateManager

Let us consider an implementation of a described above approach based on a Button control template creation. First, it is necessary to redefine a control element template. It can be done in a style or directly with help of a property setting. We will define a template through the direct setting of a Template property.

Listing 1 - Simple button definition

<Button Content="Button">
      <Button.Template>
            <ControlTemplate TargetType="Button">
                  <Grid />
            </ControlTemplate>
      </Button.Template>
</Button>

As a result of this definition, we get an empty button. So it is necessary to fill it with content.

Listing 2 - Simple button definition with contents

<Button Content="Button">
      <Button.Template>
            <ControlTemplate TargetType="Button">
                  <Grid>
                        <Border CornerRadius="3" BorderBrush="Black" BorderThickness="1" 
Background="Silver" />
                        
                        <ContentPresenter VerticalAlignment="Center" HorizontalAlignment="Center" />
                  </Grid>
            </ControlTemplate>
      </Button.Template>
</Button>   

Thanks to this definition the button has gotten borders and begun to display its content. However, in the given template there is no dynamics. If we hover the mouse cursor to the button or press it, nothing will change. It is easy to be changed. For example, in the simplest case it is possible to change a button background. For that end, if adding some control elements, it will be displayed in various states.

Listing 3 - The button definition with contents and additional layout

<Button Content="Button">
      <Button.Template>
            <ControlTemplate TargetType="Button">
                  <Grid>
                        <Border Name="NormalBackground" CornerRadius="3" 
BorderBrush="Black" BorderThickness="1" Background="Silver" />
                        <Border Name="HoverBackground" Opacity="0" 
CornerRadius="3" BorderBrush="Black" BorderThickness="3" Background="Gray" />
                        
                        <ContentPresenter VerticalAlignment="Center"
HorizontalAlignment="Center" />
                  </Grid>
            </ControlTemplate>
      </Button.Template>
</Button>   

Pay attention to the fact "HoverBackground" control element is hidden in this case (property Opacity equals 0). Our goal is to display it at the moment of the mouse cursor hovering over the button and to hide in the opposite situation. For this purpose it is necessary to define VisualStateManager object which will execute all work.

Listing 4 - The button definition with VisualStateManager

<Button Content="Button">
      <Button.Template>
            <ControlTemplate TargetType="Button">
                  <Grid>
                        <VisualStateManager.VisualStateGroups>
                              <VisualStateGroup x:Name="CommonStates">
                                    <VisualState x:Name="Normal">
                                          <Storyboard>
                                                <DoubleAnimation To="1" 
Duration="0:00:00.5" Storyboard.TargetName="NormalBackground" Storyboard.TargetProperty="Opacity"/>
                                                <DoubleAnimation To="0" 
Duration="0:00:00.5" Storyboard.TargetName="HoverBackground" Storyboard.TargetProperty="Opacity"/>
                                          </Storyboard>
                                    </VisualState>
                                    <VisualState x:Name="MouseOver">
                                          <Storyboard>
                                                <DoubleAnimation To="0" 
Duration="0:00:00.5" Storyboard.TargetName="NormalBackground" Storyboard.TargetProperty="Opacity"/>
                                                <DoubleAnimation To="1" 
Duration="0:00:00.5" Storyboard.TargetName="HoverBackground" Storyboard.TargetProperty="Opacity"/>
                                          </Storyboard>
                                    </VisualState>
                              </VisualStateGroup>
                        </VisualStateManager.VisualStateGroups>
                        
                        <Border Name="NormalBackground" CornerRadius="3" 
BorderBrush="Black" BorderThickness="1" Background="Silver" />
                        <Border Name="HoverBackground" Opacity="0"
CornerRadius="3" BorderBrush="Black" BorderThickness="3" Background="Gray" />
                        
                        <ContentPresenter VerticalAlignment="Center" 
HorizontalAlignment="Center" />
                  </Grid>
            </ControlTemplate>
      </Button.Template>
</Button>   

There is a similar way to manage a control element appearance for several states. For example, it is possible to add to the button a representation of state "pressed."

Listing 5 - The button definition with VisualStateManager which describes three states

<Button Content="Button">
      <Button.Template>
            <ControlTemplate TargetType="Button">
                  <Grid>
                        <VisualStateManager.VisualStateGroups>
                              <VisualStateGroup x:Name="CommonStates">
                                    <VisualState x:Name="Normal">
                                          <Storyboard>
                                                <DoubleAnimation To="1" 
Duration="0:00:00.5" Storyboard.TargetName="NormalBackground" Storyboard.TargetProperty="Opacity"/>
                                                <DoubleAnimation To="0" 
Duration="0:00:00.5" Storyboard.TargetName="HoverBackground" Storyboard.TargetProperty="Opacity"/>
                                                <DoubleAnimation To="0" 
Duration="0:00:00.5" Storyboard.TargetName="PressedBackground" Storyboard.TargetProperty="Opacity"/>
                                          </Storyboard>
                                    </VisualState>
                                    <VisualState x:Name="MouseOver">
                                          <Storyboard>
                                                <DoubleAnimation To="0"
Duration="0:00:00.5" Storyboard.TargetName="NormalBackground" Storyboard.TargetProperty="Opacity"/>
                                                <DoubleAnimation To="1" Duration="0:00:00.5" 
Storyboard.TargetName="HoverBackground" 
Storyboard.TargetProperty="Opacity"/>
                                                <DoubleAnimation To="0" Duration="0:00:00.5" 
Storyboard.TargetName="PressedBackground"
Storyboard.TargetProperty="Opacity"/>
                                          </Storyboard>
                                    </VisualState>
                                    <VisualState x:Name="Pressed">
                                          <Storyboard>
                                                <DoubleAnimation To="0"
Duration="0:00:00.5" Storyboard.TargetName="NormalBackground" Storyboard.TargetProperty="Opacity"/>
                                                <DoubleAnimation To="0"
Duration="0:00:00.5" Storyboard.TargetName="HoverBackground" Storyboard.TargetProperty="Opacity"/>
                                                <DoubleAnimation To="1"
Duration="0:00:00.5" Storyboard.TargetName="PressedBackground"
Storyboard.TargetProperty="Opacity"/>
                                          </Storyboard>
                                    </VisualState>
                              </VisualStateGroup>
                        </VisualStateManager.VisualStateGroups>
                        
                        <Border Name="NormalBackground" CornerRadius="3"
BorderBrush="Black" BorderThickness="1" Background="Silver" />
                        <Border Name="HoverBackground" Opacity="0"
CornerRadius="3" BorderBrush="Black" BorderThickness="3" Background="Gray" />
                        <Border Name="PressedBackground" Opacity="0"
CornerRadius="8" BorderBrush="Black" BorderThickness="1" Background="Red" />
                        
                        <ContentPresenter VerticalAlignment="Center" HorizontalAlignment="Center" />
                  </Grid>
            </ControlTemplate>
      </Button.Template>
</Button>

As a result, the given button will look differently in three various states - normal, pressed and hovered.

Creation of own control

All standard control elements have a set of predetermined states. These conditions can be used developing a new control element template. The same approach can be used to create your own control elements. Let us create a new one and add a text property to it.

Listing 6 - The definition of new control

public class SampleControl : Control
{
      public static readonly DependencyProperty SomeTextProperty =
            DependencyProperty.Register("SomeText", typeof(string),
            typeof(SampleControl), null);
      public string SomeText
      {
            get
            {
                  return GetValue(TemplateProperty) as string;
            }
            set
            {
                  SetValue(TemplateProperty, value);
            }
      }
}

Now it is necessary to define time moments when the control element will switch its state. It can be an event of receiving HTTP-request response, switching an internal element state, etc. In our case we simplify the example. We create some methods which simulate state switching.

Listing 7 - The definition of new control with switching methods

public class SampleControl : Control
{
      public static readonly DependencyProperty SomeTextProperty = 
            DependencyProperty.Register("SomeText", typeof(string), 
            typeof(SampleControl), null);
      public string SomeText
      {
            get
            {
                  return GetValue(TemplateProperty) as string;
            }
            set
            {
                  SetValue(TemplateProperty, value);
            }
      }
 
      public void DoSomething()
      {
            VisualStateManager.GoToState(this, "State1"true);
      }
 
      public void DoSomethingElse()
      {
            VisualStateManager.GoToState(this, "State2"true);
      }
 
      public void Clear()
      {
            VisualStateManager.GoToState(this, "Normal"true);
      }
}

It is necessary to add a description of states for our control element, marking with a special attribute TemplateVisualStateAttribute should be used.

Listing 8 - The describing of states for the control

[TemplateVisualState(GroupName = "CommonStates", Name = "Normal")]
[TemplateVisualState(GroupName = "CommonStates", Name = "State1")]
[TemplateVisualState(GroupName = "CommonStates", Name = "State2")]
public class SampleControl : Control
{
      public static readonly DependencyProperty SomeTextProperty = 
            DependencyProperty.Register("SomeText", typeof(string), 
            typeof(SampleControl), null);
      public string SomeText
      {
            get
            {
                  return GetValue(TemplateProperty) as string;
            }
            set
            {
                  SetValue(TemplateProperty, value);
            }
      }
 
      public void DoSomething()
      {
            VisualStateManager.GoToState(this, "State1"true);
      }
 
      public void DoSomethingElse()
      {
            VisualStateManager.GoToState(this, "State2"true);
      }
 
      public void Clear()
      {
            VisualStateManager.GoToState(this, "Normal"true);
      }
}

After that a control element template should be defined. Usage of VisualStateManager object will help to define a representation for each state. As an example we will display a text field with a name of the current state. Besides, in a state "Normal" we will display the text from "SomeText" property. In this case the following template should be defined to the control element.

Listing 9 - The describing of states for the control

<myapp:SampleControl x:Name="MyControl1">
  <myapp:SampleControl.Template>
    <ControlTemplate TargetType="myapp:SampleControl">
      <Grid>
        <vsm:VisualStateManager.VisualStateGroups>
          <vsm:VisualStateGroup x:Name="CommonStates">
            <vsm:VisualState x:Name="Normal">
              <Storyboard>
                <DoubleAnimation To="1" Duration="0:00:00.5"
Storyboard.TargetName="NormalText" Storyboard.TargetProperty="Opacity"/>
                <DoubleAnimation To="0" Duration="0:00:00.5" 
Storyboard.TargetName="State1Text" Storyboard.TargetProperty="Opacity"/>
                <DoubleAnimation To="0" Duration="0:00:00.5" 
Storyboard.TargetName="State2Text" Storyboard.TargetProperty="Opacity"/>
              </Storyboard>
            </vsm:VisualState>
            <vsm:VisualState x:Name="State1">
              <Storyboard>
                <DoubleAnimation To="0" Duration="0:00:00.5"
Storyboard.TargetName="NormalText" Storyboard.TargetProperty="Opacity"/>
                <DoubleAnimation To="1" Duration="0:00:00.5"
Storyboard.TargetName="State1Text" Storyboard.TargetProperty="Opacity"/>
                <DoubleAnimation To="0" Duration="0:00:00.5"
Storyboard.TargetName="State2Text" Storyboard.TargetProperty="Opacity"/>
              </Storyboard>
            </vsm:VisualState>
            <vsm:VisualState x:Name="State2">
              <Storyboard>
                <DoubleAnimation To="0" Duration="0:00:00.5"
Storyboard.TargetName="NormalText" Storyboard.TargetProperty="Opacity"/>
                <DoubleAnimation To="0" Duration="0:00:00.5"
Storyboard.TargetName="State1Text" Storyboard.TargetProperty="Opacity"/>
                <DoubleAnimation To="1" Duration="0:00:00.5"
Storyboard.TargetName="State2Text" Storyboard.TargetProperty="Opacity"/>
              </Storyboard>
            </vsm:VisualState>
          </vsm:VisualStateGroup>
        </vsm:VisualStateManager.VisualStateGroups>
 
        <TextBlock x:Name="NormalText" Text="{TemplateBinding SomeText}"/>
        <TextBlock x:Name="State1Text" Text="State1" Opacity="0"/>
        <TextBlock x:Name="State2Text" Text="State2" Opacity="0"/>
      </Grid>
    </ControlTemplate>
  </myapp:SampleControl.Template>
</myapp:SampleControl>

Now if the object methods are called externally, the state changes. Changing a control element state is the reason of its appearance modification.

Conclusion

Thanks to the possibility of the declarative user interfaces, building our representations of software developing has changed. The process has become more transparent, clear and convenient. The repetition of the processes is visible thanks to existing of VisualStateManager. As a result, approaches to the user interface generation are changing. Construction of representation based on states does this process more simply and logically. Difficult logic of representation can be described easier now. So then, why don't we try to use it?



©Copyright 1998-2019 ASPAlliance.com  |  Page Processed at 2019-08-21 6:46:16 PM  AspAlliance Recent Articles RSS Feed
About ASPAlliance | Newsgroups | Advertise | Authors | Email Lists | Feedback | Link To Us | Privacy | Search