Nick's .NET Travels

Continually looking for the yellow brick road so I can catch me a wizard....

Visual States in Windows Phone and Windows Applications using MvvmCross

I’m a big fan of using Visual States defined in Blend to be able to see all the different states that a page/view within your application would look like under different conditions. Most of these conditions are reflected in the state of your view model. However, there is no built in mechanism for data binding to a visual state. There are a number of different ways to tackle this problem, for example data triggers to control state changes. On technique we’ve used quite successfully is to simply bubble up appropriate state changed events from the view model, which in turn triggers a state change at the page level. In this post I’m going to walk you through how we’ve extended MvvmCross to include our own base view model and base page which allows us to trigger and track the state of the page.

We’ll start by defining an interface which will be implemented by our base view model. This includes the StateChanged event and methods for invoking a state change, ChangePageState, and querying the current state, CurrentState. You’ll notice that these methods take a type parameter, this is so that we can use an enumeration, rather than string literals to define our states.

public interface IStateAndTransitions
{
    event EventHandler<DualParameterEventArgs<string, bool>> StateChanged;
    void ChangePageState<T>(T stateName, bool useTransitions = true) where T : struct;
    T CurrentState<T>() where T : struct;
}

Next, we’ll implement the IStateAndTransitions interface in our BaseViewModel class. This class also inherits from MvxViewModel, bringing with it all the goodness of MvvmCross.

public class BaseViewModel : MvxViewModel, IStateAndTransitions
{
    public event EventHandler<DualParameterEventArgs<string, bool>> StateChanged;

    private readonly Dictionary<string, string> currentStates = new Dictionary<string, string>();
    public T CurrentState<T>() where T : struct
    {
        var current = currentStates.SafeDictionaryValue<string, string, string>(typeof(T).FullName);
        var tvalue = current.EnumParse<T>();
        return tvalue;
    }

    public void ChangePageState<T>(T stateName, bool useTransitions = true) where T : struct
    {
        var current = currentStates.SafeDictionaryValue<string, string, string>(typeof(T).FullName);
        var newState = stateName.ToString();
        if (string.IsNullOrWhiteSpace(current) || current != newState)
        {
            currentStates[typeof(T).FullName] = newState;
            StateChanged.SafeRaise(this, newState, useTransitions);
        }
    }
}

The last thing to do is to extend the MvxPhonePage to wire up an event handler for the StateChanged event. The event handler simply invokes the state change by calling GoToState on the VisualStateManager.

public class BasePhonePage : MvxPhonePage
{

    protected IStateAndTransitions StatesAndTransitionsViewModel
    {
        get
        {
            return DataContext as IStateAndTransitions;
        }
    }

    protected override void OnNavigatedTo(NavigationEventArgs e)
    {
        base.OnNavigatedTo(e);

        var satvm = StatesAndTransitionsViewModel;
        if (satvm != null)
        {
            satvm.StateChanged += ViewModelStateChanged;
        }
    }

    private void ViewModelStateChanged(object sender, DualParameterEventArgs<string, bool> e)
    {
        //var controlName = e.Parameter1;
        var stateName = e.Parameter1;
        var useTransitions = e.Parameter2;

        // Locate the control to change state of (use this Page if controlNAme is null)
        Control control = this;
        VisualStateManager.GoToState(control, stateName, useTransitions);
    }

    protected override void OnNavigatedFrom(NavigationEventArgs e)
    {
        var satvm = StatesAndTransitionsViewModel;
        if (satvm != null)
        {
            satvm.StateChanged -= ViewModelStateChanged;
        }

        base.OnNavigatedFrom(e);
    }
}

For completeness you’ll also need the parameter and dualparameter event args classes. These are used to make it easier to pass data values around when raising events.

public class ParameterEventArgs<T> : EventArgs
{
    public T Parameter1 { get; set; }

    public ParameterEventArgs(T parameter)
    {
        Parameter1 = parameter;
    }

  public static implicit operator ParameterEventArgs<T>(T parameter)
    {
        return new ParameterEventArgs<T>(parameter);
    }
}

public class DualParameterEventArgs<T1, T2> : ParameterEventArgs<T1>
{
    public T2 Parameter2 { get; set; }

    public DualParameterEventArgs(T1 parameter1, T2 parameter2):base(parameter1)
    {
        Parameter2 = parameter2;
    }

    public static implicit operator DualParameterEventArgs<T1, T2>(object[] parameters )
    {
        if(parameters==null || parameters.Length!=2) return null;
        return new DualParameterEventArgs<T1, T2>((T1)parameters[0], (T2)parameters[1]);
    }
}

Now, let’s see this in action in a view model, FirstViewModel:

public class FirstViewModel : BaseViewModel
{
    public enum FirstStates
    {
        Base,
        Loading,
        Loaded
    }

    public async Task LoadData()
    {
        ChangePageState(FirstStates.Loading);
        // Load some data (async to ensure no UI blocking)
        ChangePageState(FristStates.Loaded);
    }
}

Your page, FirstView, needs to inherit from BasePhonePage and have visual states called Loading and Loaded. The “Base” enumeration value is there just to represent the default state of the page. You should never have a state called “Base”, nor should you call ChangePageState with a value of Base. You can however, call CurrentState and compare the value to Base to see if a state has been set.

Hope this makes it easier for you to build your visual states in Blend.

Pingbacks and trackbacks (1)+

Comments are closed