July 2010

Volume 25 Number 07

UI Frontiers - The Fluid UI in Silverlight 4

By Charles Petzold | July 2010

Charles PetzoldThe term “fluid UI” has recently become common to describe UI design techniques that avoid having visual objects suddenly pop into view or jump from one location to another. Instead, visually fluid objects make more graceful entrances and transitions—sometimes as if emerging from fog or sliding into view.

In the past two installments of this column, I’ve discussed some techniques implementing fluid UI on your own. I was partially inspired by the upcoming introduction of a fluid UI feature in Silverlight 4. Now that Silverlight 4 has been officially released, that’s what I’ll be covering here. Silverlight 4’s foray into fluid UI is rather narrowly confined—it’s restricted to the loading and unloading of items in a ListBox—but it gives us some important hints on how to extend fluid UI techniques with our own implementations. More fluid UI behaviors are available in Expression Blend 4.

Templates and the VSM

If you don’t know exactly where to find the new fluid UI feature in Silverlight 4, you might search for many hours. It’s not a class. It’s not a property. It’s not a method. It’s not an event. It’s actually implemented as three new visual states on the ListBoxItem class. Figure 1 shows the documentation for that class, with the TemplateVisualState attribute items slightly rearranged in accordance with the group names.

Figure 1 The ListBoxItem Class Documentation

[TemplateVisualStateAttribute(Name = "Normal", GroupName =  
  "CommonStates")]
[TemplateVisualStateAttribute(Name = "MouseOver", GroupName = 
  "CommonStates")]
[TemplateVisualStateAttribute(Name = "Disabled", GroupName = 
  "CommonStates")]
[TemplateVisualStateAttribute(Name = "Unselected", GroupName = 
  "SelectionStates")]
[TemplateVisualStateAttribute(Name = "Selected", GroupName =  
  "SelectionStates")]
[TemplateVisualStateAttribute(Name = "SelectedUnfocused", GroupName = 
  "SelectionStates")]
[TemplateVisualStateAttribute(Name = "Unfocused", GroupName = 
  "FocusStates")]
[TemplateVisualStateAttribute(Name = "Focused", GroupName = 
  "FocusStates")]
[TemplateVisualStateAttribute(Name = "BeforeLoaded", GroupName = 
  "LayoutStates")]
[TemplateVisualStateAttribute(Name = "AfterLoaded", GroupName = 
  "LayoutStates")]
[TemplateVisualStateAttribute(Name = "BeforeUnloaded", GroupName =  
  "LayoutStates")]
public class ListBoxItem : ContentControl

The Visual State Manager (VSM) is one of the most significant changes made to Silverlight as it was being adapted from Windows Presentation Foundation. In WPF, a style or a template (almost always defined in XAML) can include elements called triggers. These triggers are defined to detect either a property change or an event, and then initiate an animation or a change to another property.

For example, a style definition for a control can include a trigger for the IsMouseOver property that sets the background of the control to a blue brush when the property is true. Or a trigger for the MouseEnter and MouseLeave events can initiate a couple of brief animations when those events occur.

In Silverlight, triggers have been largely banished and replaced with the VSM, partially to provide a more structured approach to dynamically changing the characteristics of a control at run time, and partially to avoid dealing with all the different combinations of possibilities when multiple triggers are defined. The VSM is considered to be such an improvement over triggers that it has become part of WPF in the Microsoft .NET Framework 4.

As you can see in Figure 1, the ListBoxItem control supports 11 visual states, but they’re apportioned into four groups. Within any group, one and only one visual state is active at any time. This simple rule greatly reduces the number of possible combinations. For example, you don’t have to figure out how the ListBoxItem should appear when the mouse is hovering over a selected but unfocused item; each group can be handled independently of the others.

The code part of ListBoxItem is responsible for changing visual states through calls to the static VisualStateManager.GoToState method. The control template for ListBoxItem is responsible for responding to these visual states. The template responds to a particular visual state change with a single Storyboard containing one or more animations that target elements in the visual tree. If you want the control to respond to a visual state change immediately without an animation, you can simply define the animation with a duration of 0. But why bother? It’s just as easy to use an animation to help make the control’s visuals more fluid.

The new visual states for supporting fluid UI are BeforeLoaded, AfterLoaded and BeforeUnloaded, all part of the LayoutStates group. By associating animations to these visual states, you can make items in your ListBox fade in, or grow or glide into view when they’re first added to the ListBox, and do something else when they’re removed from the ListBox.

Adapting the ListBoxItem Template

Most programmers will probably access the fluid UI feature of ListBoxItem through Expression Blend, but I’m going to show you how to do it directly in markup.

The default control template for ListBoxItem has no animations associated with the visual states in the LayoutStates group. That’s your job. Unfortunately, you can’t just “derive from” the existing ListBoxItem template and supplement it with your own stuff. You must include the whole template in your program. Fortunately, it’s a simple matter of copy and paste. In the Silverlight 4 documentation, look in the Controls section, and then Control Customization, and Control Styles and Templates, and ListBox Styles and Templates. You’ll find the default style definition for ListBoxItem (which includes the template definition) in the markup that begins:

<Style TargetType="ListBoxItem">

Under the Setter element for the Template property, you’ll see the entire ControlTemplate used to build a visual tree for each ListBoxItem. The root of the visual tree is a single-cell Grid. The VSM markup occupies a large part of the template at the top of the Grid definition. At the bottom are the actual contents of the Grid: three Rectangle shapes (two filled and one just stroked) and a ContentPresenter, like so:

<Grid ... >
  ...  <Rectangle x:Name="fillColor" ... />
  <Rectangle x:Name="fillColor2" ... />
  <ContentPresenter x:Name="contentPresenter" ... />
  <Rectangle x:Name="FocusVisualElement" ... />
</Grid>

The first two filled Rectangle objects are used to provide background shading for mouse-over and selection (respectively). The third displays a stroked rectangle to indicate input focus. The visibility of these rectangles is controlled by the VSM markup. Notice how each visual group gets its own element to manipulate. The ContentPresenter hosts the item as it’s displayed in the ListBox. Generally, the content of the ContentPresenter is another visual tree defined in a DataTemplate that’s set to the ItemTemplate property of ListBox.

The VSM markup consists of elements of type VisualStateManager.VisualStateGroups, VisualStateGroup and VisualState, all with an XML namespace prefix of “vsm.” In earlier versions of Silverlight, it was necessary to define a namespace declaration for that prefix:

xmlns:vsm="clr-namespace:System.Windows;assembly=System.Windows"

However, in Silverlight 4 you can simply delete all the vsm prefixes and forget about this namespace declaration. To make changes to this template, you’ll want to copy that whole section of markup into a resource section of a XAML file and give it a key name:

<Style x:Key="listBoxItemStyle" TargetType="ListBoxItem">
  ... </Style>

You then set this style to the ItemContainerStyle property of the ListBox:

<ListBox ... ItemContainerStyle="{StaticResource listBoxItemStyle}" ....

The “item container” is the object the ListBox creates as a wrapper for each item in the ListBox, and that’s an object of type ListBoxItem.

Once you have this ListBoxItem style and template in your program, you can make changes to it.

Fade in, Fade Out

Let’s see how this works in the context of a simple demo program. The downloadable code for this article is a solution entitled FluidUserInterfaceDemo. It consists of two programs, which you can run from my Web site at charlespetzold.com/silverlight/FluidUserInterfaceDemo. Both programs are on the same HTML page, each occupying the whole browser window.

The first program is FluidListBox. Visually, it consists of a ListBox and two buttons to add and remove items. I’ve used the same collection of grocery produce that I’ve used in my last two columns, so MainPage.xaml also contains a DataTemplate named produceDataTemplate.

I decided I wanted to start off simple and have the items fade into view when they’re added to the ListBox and fade out when they’re removed. This involves animating the Opacity property of the Grid that forms the root of the visual tree. To be the target of an animation, that Grid needs a name:

<Grid Name="rootGrid" ...>

First insert a new VisualStateGroup within the VisualStateManager.VisualStateGroups tags:

<VisualStateGroup x:Name="LayoutStates">  ...
</VisualStateGroup>

That’s where the markup goes for the BeforeLoaded, AfterLoaded and BeforeUnloaded states in the LayoutStates group.

The fade-in is the easier of the two jobs. When an item is first added to the visual tree, it’s said to be “loaded” into the visual tree. Prior to being loaded, the item has a visual state of BeforeLoaded, and then the visual state becomes AfterLoaded.

There are several ways to define the fade-in. The first requires initializing the Opacity to 0 in the Grid tag:

<Grid Name="rootGrid" Opacity="0" ... >

You then provide an animation for the AfterLoaded state to increase the Opacity property to 1 over the course of 1 second:

<VisualState x:Name="AfterLoaded">
  <Storyboard>
    <DoubleAnimation Storyboard.TargetName="rootGrid"
                     Storyboard.TargetProperty="Opacity"
                     To="1" Duration="0:0:1" />
  </Storyboard>
</VisualState>

Or you can leave the Grid opacity at its default value of 1 and provide animations for both BeforeLoaded and AfterLoaded:

<VisualState x:Name="BeforeLoaded">
  <Storyboard>
    <DoubleAnimation Storyboard.TargetName="rootGrid"
                     Storyboard.TargetProperty="Opacity"
                     To="0" Duration="0:0:0" />
  </Storyboard>
</VisualState>
                                    
<VisualState x:Name="AfterLoaded">
  <Storyboard>
    <DoubleAnimation Storyboard.TargetName="rootGrid"
                     Storyboard.TargetProperty="Opacity"
                     To="1" Duration="0:0:1" />
  </Storyboard>
</VisualState>

Notice that the Duration on the BeforeLoaded state is 0, which effectively just sets the Opacity property to 0. Using a whole Storyboard and DoubleAnimation just to set a property might seem like overkill, but it also demonstrates the flexibility of animations. The overhead is actually not very much.

The approach I personally prefer—primarily because it’s the simplest—is to leave the Opacity property of the Grid at its default value of 1 and provide only an animation for the AfterLoaded state with a From value specified, rather than a To value:

<VisualState x:Name="AfterLoaded">
  <Storyboard>
    <DoubleAnimation Storyboard.TargetName="rootGrid"
                     Storyboard.TargetProperty="Opacity"
                     From="0" Duration="0:0:1" />
  </Storyboard>
</VisualState>

Now the animation goes from the value of 0 to its base value, which is 1. You can use this identical technique with the BeforeLoaded state. But watch out: The BeforeLoaded state occurs after the ListBoxItem is created and initialized, but before it’s added to the visual tree, at which point the AfterLoaded state occurs. That’s just a tiny gap of time. You’ll get into trouble if you define an animation for BeforeLoaded but also define an empty VisualState tag for AfterLoaded:

<VisualState x:Name="BeforeLoaded">
  <Storyboard>
    <DoubleAnimation Storyboard.TargetName="rootGrid"
                     Storyboard.TargetProperty="Opacity"
                     From="0" Duration="0:0:1" />
  </Storyboard>
</VisualState>
                                    
<VisualState x:Name="AfterLoaded" />

As soon as the item is loaded, the storyboard for BeforeLoaded is terminated and you’ll get no fade-in effect. However, you can make that markup work if you also add the following:

<VisualStateGroup.Transitions>
  <VisualTransition From="BeforeLoaded"
                    To="AfterLoaded"
                    GeneratedDuration="0:0:1" />
</VisualStateGroup.Transitions>

This defines a one-second transition period between the BeforeLoaded and the AfterLoaded states. That transition period gives the BeforeLoaded animation time to complete before the AfterLoaded state shuts it off.

The fade-out process isn’t quite as straightforward. When the item is about to be removed from the ListBox, the BeforeUnloaded state is set, but then the item is immediately removed so any animation that began won’t be visible! I’ve found two approaches that work. The first defines an animation for the BeforeUnloaded state together with a transition for that state:

<VisualState x:Name="BeforeUnloaded">
  <Storyboard>
    <DoubleAnimation Storyboard.TargetName="rootGrid"
                     Storyboard.TargetProperty="Opacity"
                     To="0" Duration="0:0:1" />
  </Storyboard>
</VisualState>

<VisualStateGroup.Transitions>
  <VisualTransition From="AfterLoaded" 
                    To="BeforeUnloaded" 
                    GeneratedDuration="0:0:1" />
</VisualStateGroup.Transitions>

The second approach defines an empty tag for the BeforeUnloaded state and an animation for the VisualTransition:

<VisualStateGroup.Transitions>
  <VisualTransition From="AfterLoaded" 
                    To="BeforeUnloaded" 
                    GeneratedDuration="0:0:1">
    <Storyboard>
      <DoubleAnimation Storyboard.TargetName="rootGrid"
                       Storyboard.TargetProperty="Opacity"
                       To="0" Duration="0:0:1" />
    </Storyboard>
  </VisualTransition>
</VisualStateGroup.Transitions>

Figure 2shows the completed markup for the AfterLoaded and BeforeUnloaded states as they appear in the ListBoxItem template in the MainPage.xaml file of the FluidListBox project.

Figure 2 An Excerpt from the ListBoxItem Template in FluidListBox

<ControlTemplate TargetType="ListBoxItem">
  <Grid Name="rootGrid" Background="{TemplateBinding Background}">
    <VisualStateManager.VisualStateGroups>

      <!-- Additions to standard template -->
      <VisualStateGroup x:Name="LayoutStates">
                                    
        <VisualState x:Name="AfterLoaded">
          <Storyboard>
            <DoubleAnimation Storyboard.TargetName="rootGrid"
                             Storyboard.TargetProperty="Opacity"
                             From="0" Duration="0:0:1" />
          </Storyboard>
        </VisualState>
                                    
        <VisualState x:Name="BeforeUnloaded" />

          <VisualStateGroup.Transitions>
            <VisualTransition From="AfterLoaded" 
                              To="BeforeUnloaded" 
                              GeneratedDuration="0:0:1">
              <Storyboard>
                 <DoubleAnimation Storyboard.TargetName="rootGrid"
                                  Storyboard.TargetProperty="Opacity"
                                  To="0" Duration="0:0:1" />
              </Storyboard>
            </VisualTransition>
          </VisualStateGroup.Transitions>
        </VisualStateGroup>
        <!-- End of additions to standard template -->
            ...
  </Grid>
</ControlTemplate>

One more warning: By default, the ListBox stores its items in a VirtualizingStackPanel. This means the actual items and their containers aren't generated until they're required to be visually displayed. If you define an animation for the After-Loaded state, and then fill the ListBox up with items, the items will fade in as they're scrolled into view. This is probably undesirable. The easy solution is to replace the VirtualizingStackPanel with a regular StackPanel. The required markup on the ListBox is trivial:

<ListBox.ItemsPanel>
  <ItemsPanelTemplate>
    <StackPanel />
  </ItemsPanelTemplate>
</ListBox.ItemsPanel>

Extending to ItemsControl

Because the fluid UI feature is implemented as visual states on ListBoxItem, it isn’t available in the ItemsControl. As you know, ItemsControl simply displays a collection of items and lets the user navigate through them. There’s no concept of selection or input focus among the items. For that reason, ItemsControl doesn’t require a special class like ListBoxItem to host the items. It just uses a ContentPresenter. Because ContentPresenter derives from FrameworkElement rather than Control, it doesn’t have a template in which to define the behavior of visual states.

What you can do, however, is derive a class from ItemsControl that uses ListBoxItem to host its items. This is actually much easier than you might assume. Figure 3 shows the entire code for FluidableItemsControl.

Figure 3 The FluidableItemsControl Class

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

namespace FluidItemsControl
{
  public class FluidableItemsControl : ItemsControl
  {
    public static readonly DependencyProperty ItemContainerStyleProperty =
      DependencyProperty.Register("ItemContainerStyle",
      typeof(Style),
      typeof(FluidableItemsControl),
      new PropertyMetadata(null));

    public Style ItemContainerStyle
    {
      set { SetValue(ItemContainerStyleProperty, value); }
      get { return (Style)GetValue(ItemContainerStyleProperty); }
    }

    protected override DependencyObject GetContainerForItemOverride()
    {
      ListBoxItem container = new ListBoxItem();

      if (ItemContainerStyle != null)
        container.Style = ItemContainerStyle;

      return container;
    }

    protected override bool IsItemItsOwnContainerOverride(object item)
    {
      return item is ListBoxItem;
    }
  }
}

The crucial method is GetContainerForItemOverride. This method returns the object used to wrap each item. ItemsControl returns ContentPresenter, but ListBox returns ListBoxItem, and that’s what FluidableItemsControl returns as well. This ListBoxItem must have a style applied, and for that reason FluidableItemsControl also defines the same ItemContainerStyle property as ListBox.

The other method that should be implemented is IsItemItsOwnContainerOverride. If the item in the ItemsControl is already the same type as its container (in this case, a ListBoxItem), then there’s no reason to put it in another container. Now you can set a ListBoxItem style definition to the ItemContainerStyle property of FluidableItemsControl. The template within the style definition can be drastically simplified. It doesn’t need logic for mouse-over, selection or input focus, so all those visual states can be eliminated, as well as the three Rectangle objects.

The FluidItemsControl program shows the result. It’s pretty much the same as FluidListBox but with all the ListBox selection logic absent. The default panel for ItemsControl is a StackPanel, so that’s another simplification. To compensate for these simplifications, I’ve enhanced the animations for loading and unloading items. Now there’s an animation on the PlaneProjection transform that makes it appear as if the items are swiveling into and out of view.

Limitations and Suggestions

Even with the facility to define animations on items in an ItemsControl or ListBox, there still exists a crucial limitation: If the control incorporates a ScrollViewer, you can’t define transforms that take the item out of the box. The ScrollViewer imposes a severe clipping region that simply can’t be transgressed (as far as I’ve been able to determine). This means that techniques such as those I demonstrated in last month’s column are still valid and important in Silverlight 4.

But the use of the VSM to implement this fluid UI feature in Silverlight 4 is a good indication that the VSM is likely to play an increasingly important role in the future to link code and XAML. It’s time that we application developers started considering implementing our own visual states for custom behavior.


Charles Petzold is a longtime contributing editor to MSDN Magazine*. He’s currently writing “Programming Windows Phone 7 Series,” which will be published as a free downloadable e-book in the fall of 2010. A preview edition is currently available through his Web site, charlespetzold.com.*