Animated Visual State Transitions with the Transitioning Content Control

 

The Silverlight Toolkit is innovative in many ways, not least of which is that controls are released in one of four quality bands:

  • Mature: ready for release
  • Stable: suitable for most scenarios
  • Preview: suitable for most basic usage scenarios, may have moderate number of breaking changes as the control is developed.
  • Experimental: intended for evaluation purposes

The control I’ll be considering today was developed (and described here) by Ruurd Boeke and is currently in the Experimental band. You can expect that the API will change quite a bit, but that said, it is an enormously useful control right now; and thus I’ve submitted a video and this write-up.

What’s It For?

The goal of the Transitioning Content control is to make it easy to add animation when you are changing content within a control as demonstrated here. [You’ll need to click on DomainUpDown on the left (and surprisingly, not on TransitioningContent!) and Animations on top. The following cropped image illustrates where to click, but provides only a shadow of the impact

DemoTransition

Getting There In 3 Steps

To make this crystal clear, and to show how easy it really is to use this control, we’ll build the example three times: first with a Content Control, then with a Transitioning Content Control, and finally, adding data binding and the ability to transition more complex objects.

Starting Simple

Version 0 begins with a grid with two columns. The left column contains a ContentControl and the right a button. Here is the complete Xaml:

<UserControl x:Class="tccDemo.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="300" Height="150">
<StackPanel Background="Bisque">

<ContentControl x:Name="cc1"
Content="Click button to change."
HorizontalAlignment="Center"
Margin="20"
FontSize="18" />

<Button x:Name="doChange"
Content="Change"
Width="80"
Height="30"
HorizontalAlignment="Center"
FontSize="14"/>

</StackPanel>
</UserControl>

The job of the ContentControl is to hold a single piece of content: in this case a string. The button’s job is to cause that content to change, which we do programmatically in the button’s click event handler in MasterPage.xaml.cs, shown in full:

using System;
using System.Windows;
using System.Windows.Controls;

namespace tccDemo
{
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
doChange.Click += new RoutedEventHandler( doChange_Click );
}

void doChange_Click( object sender, RoutedEventArgs e )
{
Random random = new Random();
cc1.Content = random.NextDouble().ToString();
}
} // end class
} // end namespace

Each time the button is clicked, a new value is displayed.

For more on Skinnable Custom Controls, see the blog series that starts here, or consider these videos (click on the image to go to the video):

SkinnableCustomControlsPart1a SkinnableCustomControlsPart2a
SkinnableCustomControlsPart3 SkinnableCustomControlsPart4

Before we dive into the TransitioiningContent control and how it does its work, let’s look at how to use it. We start by replacing the ContentControl with a TransitioningContentControl, but to do this we need to add a reference to System.Windows.Controls.Layout.Toolkit in the references and a namespace to the top of the Xaml file

xmlns:layout="clr-namespace:System.Windows.Controls;
assembly=System.Windows.Controls.Layout.Toolkit"

With that in place we can modify MainPage.xaml to replace the ContentControl with the TransitioningContentControl and replace the Change button with two buttons: one for Up and one for down. Here is the complete Xaml:

<UserControl x:Class="tccDemo.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:layout="clr-namespace:System.Windows.Controls;
assembly=System.Windows.Controls.Layout.Toolkit"
Width="400" Height="200">
<StackPanel Background="Bisque">
<layout:TransitioningContentControl x:Name="tcc"
HorizontalAlignment="Center"
Margin="30"
FontSize="18"
Content="Click buttons to change content" />

<Button x:Name="UpButton"
Content="Up"
Width="100"
Height="30"
Margin="10"
FontSize="14"/>
<Button x:Name="DownButton"
Content="Down"
Width="100"
Height="30"
Margin="10"
FontSize="14"/>
</StackPanel>
</UserControl>

The code is modified only to set the Transition property of the TransitioningContentControl.  Here is the complete code behind file:

using System;
using System.Windows;
using System.Windows.Controls;

namespace tccDemo
{
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
UpButton.Click += new RoutedEventHandler( UpButton_Click );
DownButton.Click += new RoutedEventHandler( DownButton_Click );
}

void DownButton_Click( object sender, RoutedEventArgs e )
{
tcc.Transition = "DownTransition";
Random random = new Random();
tcc.Content = random.NextDouble().ToString();
}

void UpButton_Click( object sender, RoutedEventArgs e )
{
tcc.Transition = "UpTransition";
Random random = new Random();
tcc.Content = random.NextDouble().ToString();
}

}
}

Here is the effect:

Adding DataBinding and Objects

In both of the examples so far, the content has been a simple string. It is possible, however, to provide a more complex object, by modifying the TransitioningContentControl and using an explicit ContentTemplate.

<layout:TransitioningContentControl   
x:Name="tcc"
Margin="20"
FontSize="18"
HorizontalAlignment="Center"
Content="Use buttons...">
<layout:TransitioningContentControl.ContentTemplate>
<DataTemplate>
<StackPanel >
<TextBlock Text="{Binding Title }"
FontFamily="Georgia"
FontSize="14" />
<TextBlock Text="{Binding Author }"
FontFamily="Georgia"
FontSize="14" />
</StackPanel>
</DataTemplate>
</layout:TransitioningContentControl.ContentTemplate>
</layout:TransitioningContentControl>

This follows all the normal conventions of using a ContentTemplate. We fill it with a DataTemplate which holds a StackPanel, allowing us to place two TextBlocks, both of which use binding syntax to indicate that they are going to bind to the Title and Author properties of whatever object they are given, respectively.

The rest of the Xaml file is unchanged.

We need a data object, and so we create as simple a data object as possible to illustrate this idea; noting that of course you can get your data from a database, from an XML file, etc.  Here is the complete contents of Book.cs including the static property we’ll use to obtain some pre-created data,

using System.Collections.Generic;

namespace tccDemo
{
public class Book
{
public string Title { get; set; }
public string Author { get; set; }

public static List<Book> Books
{
get
{
List<Book> theBooks = new List<Book>();
theBooks.Add( new Book()
{ Title = "The Raw Shark Texts", Author = "Steven Hall" } );
theBooks.Add( new Book()
{ Title = "Columbine", Author = "Dave Cullen" } );
theBooks.Add( new Book()
{ Title = "Unfriendly Fire", Author = "Dr. Nathaniel Frank" } );
theBooks.Add( new Book()
{ Title = "The Inheritance", Author = "Dave Sanger" } );
theBooks.Add( new Book()
{ Title = "Sir Gawain and the Green Knight",
Author = "Simon Armitage" } );
theBooks.Add( new Book()
{ Title = "The Superorganism", Author = "Holldobler and Wilson" } );
return theBooks;
}
}
}
}

 

MainPage.xaml.cs is modified somewhat more significantly, to hold a membervariable of type List<Book> (thus avoiding having to “get” the data repeatedly) and a counter as a convenience so that we can cycle through our somewhat meager collection.  Here is the complete MainPage.xaml.cs

using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;

namespace tccDemo
{
public partial class MainPage : UserControl
{

private List<Book> books = Book.Books;
private int counter = 0;

public MainPage()
{
InitializeComponent();
tcc.Transition = "Normal";
tcc.Content = books[counter++];
UpButton.Click += new RoutedEventHandler( UpButton_Click );
DownButton.Click += new RoutedEventHandler( DownButton_Click );
}

void UpButton_Click( object sender, RoutedEventArgs e )
{
tcc.Transition = "UpTransition";
tcc.Content = GetBook();
}

void DownButton_Click( object sender, RoutedEventArgs e )
{
tcc.Transition = "DownTransition";
tcc.Content = GetBook();
}

public Book GetBook()
{
if ( ++counter >= books.Count )
counter = 0;
return books[counter];
}
}
}

The constructor sets the initial visual state to Normal and sets the content of the TransitioningContentControl to the first book in the collection. It then sets up the two event handlers. The job of each is to set the Transition state and then call the helper method that gets the next book in the collection.

Visual State Ambivalence

The TransitioningContent is a bit ambivalent about its visual states. There are four states that are hardwired into the control as it is currently written:

  • Default
  • Normal
  • UpTransition
  • DownTransition

However, if you examine the attributes at the top of the class (used to signal, for example, both the Visual State Manager and tools like Blend what visual states the class supports) you’ll find this:

[TemplateVisualState(GroupName = PresentationGroup, 
Name = NormalState)]
[TemplateVisualState(GroupName = PresentationGroup,
Name = DefaultTransitionState)]

[TemplatePart(Name = PreviousContentPresentationSitePartName,
Type = typeof(ContentControl))]
[TemplatePart(Name = CurrentContentPresentationSitePartName,
Type = typeof(ContentControl))]
public class TransitioningContentControl : ContentControl

There are precisely two visual states made visible to Blend and the VSM.  The net effect is that you can certainly use the UpTransition and DownTransition, but they were added as examples of how you can freely extend this class with any transitions you like.

Here’s how it works. The TransitioningContentControl consists of two parts both of type ContentControl: PreviousContentPresentationSitePartName and CurrentContentPresentationSitePartName. 

To add an animated transition from content A to content B you need only hand the two to this control and tell it, by passing in a string, what storyboard to invoke.  If you pass in the string TransitionUp or TransitionDown then it already knows what storyboard to invoke, as Ruurd Boeke wrote those and put them in the Resources section of TransitioningContentControl.xaml.  Here, for example, is his UpTransition:

<vsm:VisualState x:Name="UpTransition">
<Storyboard>
<DoubleAnimationUsingKeyFrames
BeginTime="00:00:00"
Storyboard.TargetName="CurrentContentPresentationSite"
Storyboard.TargetProperty="(UIElement.Opacity)">
<SplineDoubleKeyFrame KeyTime="00:00:00" Value="0"/>
<SplineDoubleKeyFrame KeyTime="00:00:00.300" Value="1"/>
</DoubleAnimationUsingKeyFrames>

<DoubleAnimationUsingKeyFrames
BeginTime="00:00:00"
Storyboard.TargetName="CurrentContentPresentationSite"
Storyboard.TargetProperty="(UIElement.RenderTransform).
(TransformGroup.Children)[3].(TranslateTransform.Y)">
<SplineDoubleKeyFrame KeyTime="00:00:00" Value="30"/>
<SplineDoubleKeyFrame KeyTime="00:00:00.300" Value="0"/>
</DoubleAnimationUsingKeyFrames>

<DoubleAnimationUsingKeyFrames
BeginTime="00:00:00"
Storyboard.TargetName="PreviousContentPresentationSite"
Storyboard.TargetProperty="(UIElement.Opacity)">
<SplineDoubleKeyFrame KeyTime="00:00:00" Value="1"/>
<SplineDoubleKeyFrame KeyTime="00:00:00.300" Value="0"/>
</DoubleAnimationUsingKeyFrames>

<DoubleAnimationUsingKeyFrames
BeginTime="00:00:00"
Storyboard.TargetName="PreviousContentPresentationSite"
Storyboard.TargetProperty="(UIElement.RenderTransform).
(TransformGroup.Children)[3].(TranslateTransform.Y)">
<SplineDoubleKeyFrame KeyTime="00:00:00" Value="0"/>
<SplineDoubleKeyFrame KeyTime="00:00:00.300" Value="-30"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</vsm:VisualState>

The effect of this is to target first the opacity of the current content, which will go from 0 to 1 over 3/10 of a second. During that same time period it will move up the Y axis from 30 to 0 (remember that the Y axis counts up as it moves down the screen).  In the second half of the animation the targetproperty is the opacity of the previous content which fades away from 1 to 0 over that same 3/10 of a second, while the content itself moves up from 0 to –30.

Notice that the storyboard is within the VisualState whose name is UpTransition.

The class itself has a public property named Transition:

/// <summary>
/// Gets or sets the name of the transition to use.
/// These correspond directly to the VisualStates inside
/// the PresentationStates group.
/// </summary>
public string Transition
{
get { return GetValue(TransitionProperty) as string; }
set { SetValue(TransitionProperty, value); }
}

This acts as a front for the Dependency Property which is registered immediately below

public static readonly DependencyProperty TransitionProperty =
DependencyProperty.Register(
"Transition",
typeof(string),
typeof(TransitioningContentControl),
new PropertyMetadata(OnTransitionPropertyChanged));

A quick review of the Silverlight documentation reveals the meaning of each of the four parameters:

DPRegister

Notice that the type of the TransitionProperty is string, and parenthetically, notice that the final parameter, typeMetadata,  is explicitly noted for usage with a PropertyChangedCallback, which is what is done here.

OnTransitionPropertyChanged is overloaded in the implementation, but the net effect is to set the source of the content control and to set the string representing the new transition and then to call ChangeTransition whose job is to make sure it is safe to set a new transition, and then to obtain the PresentationState Visual State group and then to look to see if there is a state for the string passed in. If so, that transition is set as the new value for the transition.

Thus, with this somewhat unusual control,  you can modify the visual states within the PresentationGroup) without subclassing, and by doing so (and providing a storyboard) you can add any transition you like, which you can then invoke by passing in its name!

Caveat! As noted earlier, this control is in the experimental band, and this API is very likely to change.

I hope you found this bit of control spelunking as interesting as I did sorting it out; and it is just fine to set all of the details aside and just use the control in conjunction with other controls to create animated transitions without over-worrying about how it is doing its magic.

About Jesse Liberty

Jesse Liberty has three decades of experience writing and delivering software projects and is the author of 2 dozen books and a couple dozen online courses. His latest book, Building APIs with .NET will be released early in 2025. Liberty is a Senior SW Engineer for CNH and he was a Senior Technical Evangelist for Microsoft, a Distinguished Software Engineer for AT&T, a VP for Information Services for Citibank and a Software Architect for PBS. He is a Microsoft MVP.
This entry was posted in z Silverlight Archives and tagged , . Bookmark the permalink.

2 Responses to Animated Visual State Transitions with the Transitioning Content Control

Comments are closed.