WPF/Silverlight visual states with DataTemplates

Consider a common task – you need to switch between different screens in your application, that represent various states (e.g. Normal/Loading/Error). How are you going to implement this?

You can start with VisualStateManager and define appropriate visual states. Well, this will surely do the thing, but you will have to create lots of controls and manage their Visibility or Opacity. And what if you have a complex markup? And I suppose that switching the state will not remove invisible items from the visual tree. And even more, with VisualStateManager you are bound to Expression Blend, because, you know, it’s not very convenient to edit VSM settings by hands in XAML (which I prefer, but it is a topic for another article). The second option that may fit well, if you just need something like “I’m busy!” overlay is Silverlight Activity Control. Works like a charm with RIA services, but I find it limited for complex scenarios (and it’s not available for WPF).

I’ve found another approach (it becomes especially useful with MVVM and improved DelegateCommand implementations – I will blog about this in a few days). The idea behind this technique is quite simple. You define different templates for different page/window/control states. Yeah, that’s it. Let’s start with our implementation.

Our application will have only one page with 2 different states:

  1. A welcome screen, that hosts a button to start ‘the heavy’ calculation.
  2. Busy window” that shows information about the current processing progress.

I decided to create a simple ViewModel as a backing storage for our UI properties. It has two properties – IsBusy that says whether some hard work is executing now, and Progress – simple execution progress indicator. Method StartCalculation runs a new thread executes a number of loop steps. On each step the executing thread is suspended for 100ms (to imitate a slow operation). Normally you will create a DelegateCommand that will handle all the execution logic, but I decided to keep things simple.

public sealed class MainWindowViewModel: INotifyPropertyChanged
{
    public const string IsBusyProperty = "IsBusy";
    private bool _isBusy;
    public bool IsBusy
    {
        get { return _isBusy; }
        set
        {
            if (_isBusy == value)
            {
                return;
            }

            _isBusy = value;
            OnPropertyChanged(IsBusyProperty);
        }
    }

    public const string ProgressProperty = "Progress";
    private int _progress;
    public int Progress
    {
        get { return _progress; }
        set
        {
            if (_progress == value)
            {
                return;
            }

            _progress = value;
            OnPropertyChanged(ProgressProperty);
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
    private void OnPropertyChanged(String property)
    {
        var handler = PropertyChanged;
        if (handler != null)
        {
            handler(this, new PropertyChangedEventArgs(property));
        }
    }

    public void StartCalculation()
    {
        IsBusy = true;
        ThreadPool.QueueUserWorkItem(_ =>
        {
            for (int i = 0; i <= 50; i++)
            {
                int i1 = i;
                Deployment.Current.Dispatcher.BeginInvoke(() => Progress = i1);
                Thread.Sleep(100);
            }

            Deployment.Current.Dispatcher.BeginInvoke(() => IsBusy = false);
        });
    }
}

Let’s build a simple user interface (for now, without any states, just to check if our threading logic works)

<UserControl 
    x:Class="DataTemplateStateApplication.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:ViewModels="clr-namespace:DataTemplateStateApplication.ViewModels"
    xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity" 
    xmlns:ei="clr-namespace:Microsoft.Expression.Interactivity.Core;assembly=Microsoft.Expression.Interactions">

    <UserControl.DataContext>
        <ViewModels:MainWindowViewModel/>
    </UserControl.DataContext>

    <Grid x:Name="LayoutRoot" Background="White">
        <Grid HorizontalAlignment="Center" VerticalAlignment="Center">
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="Auto"/>
            </Grid.RowDefinitions>
		    
            <Button Width="60" Height="24" Content="Test">
                <i:Interaction.Triggers>
                    <i:EventTrigger EventName="Click">
                        <ei:CallMethodAction MethodName="StartCalculation" TargetObject="{Binding}"/>
                    </i:EventTrigger>
               </i:Interaction.Triggers>
            </Button>
		    
            <ProgressBar 
                Minimum="0" Maximum="50"
                Value="{Binding Progress}" 
                Height="24" Width="250" 
                Grid.Row="1" Margin="0,8,0,0"/>
        </Grid>
    </Grid>
</UserControl>

Note that if you use Visual Studio, you will have to manually add references to Microsoft.Expression.Interactions.dll and System.Windows.Interactivity.dll

I added a simple event trigger that calls StartCalculation method of our ViewModel when user clicks the button.

Now let’s move to the article subject. Define two DataTemplates in UserControl.Resources section:

<UserControl.Resources>
    <DataTemplate x:Key="NormalTemplate">
        <Button Width="60" Height="24" Content="Test">
            <i:Interaction.Triggers>
                <i:EventTrigger EventName="Click">
                    <ei:CallMethodAction MethodName="StartCalculation" TargetObject="{Binding}"/>
                </i:EventTrigger>
            </i:Interaction.Triggers>
        </Button>
    </DataTemplate>
    <DataTemplate x:Key="BusyTemplate">
        <ProgressBar Minimum="0" Maximum="50" Value="{Binding Progress}" 
                     Height="24" Width="250"/>
    </DataTemplate>
</UserControl.Resources>

These templates describe two different presentations for a single MainWindowViewModel. Now we just have to provide some logic for switching between these templates when IsBusy property changes. Remove old layout markup (Button and Progressbar) and replace it with ContentPresenter with Content property bound to MainWindowViewModel:

<ContentPresenter Content="{Binding}">
    <i:Interaction.Triggers>
        <ei:DataTrigger Binding="{Binding IsBusy}" Value="True">
            <ei:ChangePropertyAction PropertyName="ContentTemplate" Value="{StaticResource BusyTemplate}"/>
        </ei:DataTrigger>
        <ei:DataTrigger Binding="{Binding IsBusy}" Value="False">
            <ei:ChangePropertyAction PropertyName="ContentTemplate" Value="{StaticResource NormalTemplate}"/>
        </ei:DataTrigger>
    </i:Interaction.Triggers>
</ContentPresenter>

Those DataTriggers from Microsoft.Expression.Interactions assembly do all the magic. They analyze the current value of IsBusy property and change the ContentPresenter.ContentTemplate value to the appropriate template.

That’s it. This technique can easily be used with more than 2 states, just declare new templates and triggers. Works with WPF too.

Download sources