Data bind visual states

The Visual State Manager in WPF and Silverlight is imperative. It has a method called “GoToState”. You invoke this method when you want the visual state to change. Data binding, on the other hand, is declarative. We don’t say when to do something, we just say what it should be. I’d like visual states to be more declarative.

Shane Holder asked if I had a solution to this problem. He used a “GoToStateAction” in Blend to control the visual state of a window. He wanted the visual state to be the current value of a property in his view model. His property type was an enumeration that matched the names of the visual states.

image He set the trigger to a PropertyChangedTrigger and bound it to the enumeration property. He then bound the StateName to the same property. This worked correctly when the property changed, but did not set the visual state on load.

The problem is in the name: “GoToStateAction”. This behavior is an action that is invoked on a specific event. It is imperative. You can get this working with more imperative code (eg. code behind, or a second GoToStateAction that triggers on the Window Loaded event). Or you can write a declarative behavior.

BindVisualStateBehavior

Add the following class to your WPF or Silverlight 4 project:

using System.Windows;
using System.Windows.Interactivity;
using Microsoft.Expression.Interactivity;

namespace BindingVisualStatesSL4
{
    public class BindVisualStateBehavior : Behavior<FrameworkElement>
    {
        public static DependencyProperty StateNameProperty = DependencyProperty.Register(
            "StateName",
            typeof(string),
            typeof(BindVisualStateBehavior),
            new PropertyMetadata(VisualStatePropertyChanged));
        private bool _initialized = false;

        public string StateName
        {
            get { return (string)GetValue(StateNameProperty); }
            set { SetValue(StateNameProperty, value); }
        }

        public void UpdateVisualState(string visualState)
        {
            if (AssociatedObject != null)
            {
                FrameworkElement stateTarget;
                if (VisualStateUtilities.TryFindNearestStatefulControl(AssociatedObject, out stateTarget))
                {
                    bool useTransitions = _initialized;
                    VisualStateUtilities.GoToState(stateTarget, visualState, useTransitions);
                    _initialized = true;
                }
            }
        }

        private static void VisualStatePropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
        {
            ((BindVisualStateBehavior)obj).UpdateVisualState((string)args.NewValue);
        }
    }
}

Note: this does not work in Silverlight 3 (and hence Windows Phone 7). Behavior properties were not bindable until 4.

This class uses the following assemblies, so add references if necessary:

  • Microsoft.Expression.Interactions
  • System.Windows.Interactivity

That first assembly comes from Blend, so you might not have it if you don’t have Blend installed. But then again, if you are not using Blend, binding visual states declaratively is the least of your problems. Defining those visual states manually in XAML is going to be a chore.

image Once you add this class (and compile), then it will appear in the Blend assets pane. Drag this behavior onto a component on your art board. Then bind the StateName property. You can use either a string or an enumeration. The names of the enumeration values must match the names of your visual states.

Compass example

Download the attached source code for an example of this behavior in both WPF and Silverlight 4. The example shows a compass. Select a direction radio button to move the compass needle.

Get Microsoft Silverlight

Both the radio buttons and the visual state are bound to a simple data model. I’m not even using MVVM.

public class Compass : INotifyPropertyChanged
{
    private CompassDirection _direction = CompassDirection.East;

    public CompassDirection Direction
    {
        get { return _direction; }
        set
        {
            if (_direction != value)
            {
                _direction = value;
                FirePropertyChanged("Direction");
            }
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    private void FirePropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }
}

I use a value converter to bind the enumeration to the IsChecked property of the radio buttons. I found this technique on Stack Overflow.

Update Controls

You might have noticed in the code above that I did not use Update Controls. Instead, I implemented the dreaded INotifyPropertyChanged. While the BindVisualStateBehavior will work with Update Controls, I wanted to demonstrate that it did not depend upon it.

Update Controls is all about replacing imperative code with declarative code. Just like GoToState, PropertyChanged is imperative. You have to fire the event at just the right time. So in spirit, this behavior is related to Update Controls.

In practice, however, the behavior is useful even if you don’t use Update Controls. I was originally planning on making it part of the library, but instead I decided to simply post the source code. Please post a comment if you believe I should include it.