Variable sized tiles in Windows Store Apps

One of the common requests I hear when training customers on Windows 8 is “How do I create variable sized tiles in a GridView?” The Windows Store app utilize this technique where different tiles have different sizes to promote content.

It turns out this is relatively easy in HTML – which is probably one of the reasons Microsoft chose to use that visual platform to build the app.  In XAML however, this is a bit more difficult.  There are many posts which detail using a VariableSizeWrapGrid with a GridView to achieve a similar effect – I like Mike Taulty’s blog on the approach.  And this does work as long as you don’t want to have different sized rows and columns, and you don’t have too many items.  The problem is that the VariableSizeWrapGrid doesn’t support virtualization or incremental loading – both key technologies if you have a lot of items.

An alternative approach to this is to do the layout yourself and use a Canvas as the panel – in this sense, you (the programmer) have to manage the layout and it’s not done automatically as rows and columns, but the upside is you have complete control over the layout and performance is greatly improved.

For a test, I chose to enumerate the available colors and then randomly create rows and columns – here’s a single run:

Notice that in this case the sample has two items side-by-side in the same column, and also has different sized columns and items that span rows – in fact, because I’m using a Canvas as my panel, I can use any sized element I choose – I’m not required to enforce columns and rows at all like I am with the grid-based panels.

The code is relatively straight-forward – I’m using MVVM so I put the Top/Width/Height into properties on the ColorViewModel:

public sealed class ColorViewModel : SimpleViewModel
{
    #region Data
    private int _heightPercent;
    private double _height, _width;
    private double _top, _left;
    #endregion

    /// <summary>
    /// Color for this item
    /// </summary>
    public string Color { get; set; }

    /// <summary>
    /// Index of the item (used for label)
    /// </summary>
    public int Index { get; set; }

    /// <summary>
    /// Row for the item (used for label)
    /// </summary>
    public int Row { get; set; }

    /// <summary>
    /// Column for the item (used for label)
    /// </summary>
    public int Col { get; set; }

    /// <summary>
    /// Left position relative to (0,0).
    /// </summary>
    public double Left
    {
        get { return _left; }
        set { SetPropertyValue(ref _left, value); }
    }

    /// <summary>
    /// Top position - changes when the Height of the GridView
    /// is altered (i.e. screen orientation)
    /// </summary>
    public double Top
    {
        get { return _top; }
        set { SetPropertyValue(ref _top, value); }
    }

    /// <summary>
    /// Width of this item
    /// </summary>
    public double Width
    {
        get { return _width; }
        set { SetPropertyValue(ref _width, value); }
    }

    /// <summary>
    /// Height to use for this item - calculated by the MainViewModel
    /// once it knows the actual height of the GridView using the Height %
    /// </summary>
    public double Height
    {
        get { return _height; }
        set { SetPropertyValue(ref _height, value); }
    }

    /// <summary>
    /// How much of the height to take up as a %
    /// </summary>
    public int HeightPercent
    {
        get { return _heightPercent; }
        set { SetPropertyValue(ref _heightPercent, value); }
    }

    /// <summary>
    /// This indicates if the cell is shared with another item in the
    /// same column/row (side-by-side)
    /// </summary>
    public bool IsSplitCell { get; set; }

    /// <summary>
    /// Returns a string that represents the current object.
    /// </summary>
    public override string ToString()
    {
        return string.Format("{0}: {1} ({2}x{3}) {4}% [{5},{6}]", Index, Color, Width, Height, HeightPercent, Col, Row);
    }
}

These properties are then calculated by the MainViewModel for each of the known colors – the position is randomly determined here just for example purposes, presumably in a real application (such as the Windows Store app) there would be an XML data file pulled from a server to display the data – but in any case there would be an known row/column and size for each element. Note there is a little code at the end of each loop iteration to ensure we always end the height of the column at 100%. The two important bits of code here are the ViewHeight property and the constructor which loads the items.

public sealed class MainViewModel : SimpleViewModel
{
    #region Data
    private readonly Random _rng = new Random();
    private double _viewHeight = Double.NaN, _viewWidth;
    private readonly List<ColorViewModel> _backingStore;
    #endregion

    /// <summary>
    /// This is set to the actual height of the panel 
    /// it then calculates the proper height and top of each item
    /// </summary>
    public double ViewHeight
    {
        get { return _viewHeight; }
        set
        {
            SetPropertyValue(ref _viewHeight, value);

            double top = 0;
            for (int index = 0; index < _backingStore.Count; index++)
            {
                var cvm = _backingStore[index];
                if (cvm.Row == 0)
                    top = 0;

                cvm.Height = _viewHeight*(cvm.HeightPercent/100.0)*.9;

                if (index > 0 && _backingStore[index - 1].IsSplitCell)
                {
                    cvm.Top = _backingStore[index - 1].Top;
                }
                else
                {
                    cvm.Top = top;
                    top += cvm.Height;
                }
            }
        }
    }

    /// <summary>
    /// The calculated width of the panel - this is required so we get scrollbars in the GridView.
    /// </summary>
    public double ViewWidth
    {
        get { return _viewWidth; }
        set { SetPropertyValue(ref _viewWidth, value); }
    }

    /// <summary>
    /// The list of colors
    /// </summary>
    public IList<ColorViewModel> Colors { get; private set; }

    /// <summary>
    /// Constructor
    /// </summary>
    public MainViewModel()
    {
        _backingStore = typeof(Colors).GetTypeInfo().DeclaredProperties
                                        .Select(p => new ColorViewModel { Color = p.Name })
                                        .ToList();

        int currentColumn = 0;
        double currentWidth = 0;

        // Calculate the position of each item. 
        for (int i = 0; i < _backingStore.Count; )
        {
            int columnWidth = _rng.Next(200, 500);
            int numberOfColors = _rng.Next(1, 7);
            int trackPct = 0;

            // Create a single column
            for (int c = 0; c < numberOfColors && trackPct < 95 && i < _backingStore.Count; i++, c++)
            {
                ColorViewModel cvm = _backingStore[i];
                cvm.Index = i + 1;
                cvm.Col = currentColumn;
                cvm.Row = c;
                cvm.Left = currentWidth;

                // Decide the height of this item.
                int maxH = Math.Min(100 - trackPct, 100/numberOfColors);
                int h = _rng.Next(15, maxH);

                // Allow it to share row with second item
                if (c > 0 && _backingStore[i-1].IsSplitCell)
                {
                    var previousCell = _backingStore[i - 1];
                    cvm.Left = previousCell.Left + previousCell.Width;
                    cvm.Width = previousCell.Width;
                    cvm.HeightPercent = previousCell.HeightPercent;
                    c--;
                }
                else
                {
                    trackPct += h;
                    cvm.HeightPercent = h;

                    if (c > 0 && numberOfColors > 2 && _rng.Next(4) == 1) // 1/4 chance
                    {
                        cvm.Width = (columnWidth / 2.0) - 5;
                        cvm.IsSplitCell = true;
                    }
                    else
                        cvm.Width = columnWidth;
                }
            }

            // Make sure we always end on 100%
            _backingStore[i - 1].HeightPercent += 100 - trackPct;
            _backingStore[i - 1].Width = columnWidth;
            _backingStore[i - 1].IsSplitCell = false;

            currentColumn++;
            currentWidth += columnWidth;
        }

        Colors = _backingStore;
        ViewWidth = currentWidth;
    }
}

The last piece of the puzzle is the XAML – in order to properly size the height, we need the actual height of the GridView itself, so the code behind hooks the SizeChanged event on the GridView and then passes the newly calculated size onto the MainViewModel:

private void OnPanelSizeChanged(object sender, Windows.UI.Xaml.SizeChangedEventArgs e)
{
    // Populate DC after first resize so we don't see small-ish items on first render.
    if (DataContext == null)
    {
        DataContext = new MainViewModel();
    }

    // Set the new actual height
    MainViewModel vm = (MainViewModel) DataContext;
    vm.ViewHeight = e.NewSize.Height;
}

We also need to respect the Canvas.Left and Canvas.Top properties – this would normally be done in an ItemContainerStyle, but unfortunately setting attached properties is not currently supported. To compensate for this, we override the GridView and set our properties onto each ItemContainer in the PrepareContainerForOverride method:

/// <summary>
/// This is here just to create a binding for the Height/Width on the GridViewItem.
/// WinRT currently doesn't support attached properties in Style setters.
/// </summary>
public class VariableSizedGridView : GridView
{
    protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
    {
        BindingOperations.SetBinding(element, Canvas.LeftProperty, new Binding { Path = new PropertyPath("Left") });
        BindingOperations.SetBinding(element, Canvas.TopProperty, new Binding { Path = new PropertyPath("Top") });
        BindingOperations.SetBinding(element, WidthProperty, new Binding { Path = new PropertyPath("Width") });
        BindingOperations.SetBinding(element, HeightProperty, new Binding { Path = new PropertyPath("Height") });

        base.PrepareContainerForItemOverride(element, item);
    }
}

And, of course we replace the panel for the derived GridView with a Canvas:

<differentSizedTiles:VariableSizedGridView ItemsSource="{Binding Colors}" Grid.Column="1" Grid.Row="1">
            
    <GridView.ItemContainerStyle>
        <Style TargetType="GridViewItem">
            <Setter Property="HorizontalContentAlignment" Value="Stretch"/>
            <Setter Property="VerticalContentAlignment" Value="Stretch"/>
        </Style>
    </GridView.ItemContainerStyle>
            
    <GridView.ItemsPanel>
        <ItemsPanelTemplate>
            <Canvas SizeChanged="OnPanelSizeChanged" Width="{Binding ViewWidth}" />
        </ItemsPanelTemplate>
    </GridView.ItemsPanel> 
            
    <GridView.ItemTemplate>
        <DataTemplate>
            <Grid Margin="5" ToolTipService.ToolTip="{Binding}">
                <Rectangle Fill="{Binding Color}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />
                <Border Background="#30000000" VerticalAlignment="Top" HorizontalAlignment="Stretch" Height="40">
                    <TextBlock Text="{Binding}" Margin="10" Style="{StaticResource ItemTextStyle}" />
                </Border>
            </Grid>
        </DataTemplate>
    </GridView.ItemTemplate>
</differentSizedTiles:VariableSizedGridView>

Voila! Note that we still don’t have UI virtualization – it turns out that you currently cannot create a custom virtualized panel, the support isn’t exposed yet in WinRT. But even still, the performance is easily 100x better than the VariableSizeGridPanel. The code is slightly more complex, but not overly so. Here’s the final project if you want to play with it yourself. Have fun!

.NET Bio 1.1 alpha available now

One of the open source projects I’m actively involved in is a bioinformatics library for .NET called, appropriately enough, .NET Bio. You can check it out at bio.codeplex.com. We have just put out the alpha version of the next release for community testing – this has several significant changes in it:

  1. A new (more standard) implementation of the Smith-Waterman alignment algorithm.
  2. A new (more standard) implementation of the Needleman-Wunsch alignment algorithm.
  3. Several improvements to the Parallel DeNovo assembler (PADENA)
  4. Changes to the NUCmer pairwise aligner to evaluate both the forward and reverse strands of the query sequence(s).
  5. Better file compatibility with several standardized formats.
  6. and lots of little performance tuning and bug fixes throughout.

Biology is one of those areas of science which needs high performance algorithms and computing power – we utilize the .NET Parallel Framework (PFx) all over the place to attempt to improve algorithmic performance and provide some great tools for analysis of biological sequences! Check it out at .NET Bio 1.1 Alpha Download

Synchronizing Collections in Windows Store Apps

One issue that often comes up when building WSAs is managing selection in your filled/snapped/full screen views.  The goal, of course, is to provide a reasonable view as the user snaps your application which is often done by providing two UIs within the page – one with a GridView for filled or full screen and the second with a ListView for snapped view.  You hide and show the two views using the Visual State Manager when using the normal templates.

If selection is provided, then ideally it would be carried over when changing views – this actually isn’t hard – it can certainly be done manually and if you only allow a single selection it’s dead easy even with MVVM.  Multi-selection is a bit trickier.  Enter the SynchronizedCollectionBehavior in MVVMHelpers.  It allows the UI to bind a collection of “selected items” to a collection in the ViewModel – so the ViewModel can track and alter the selection, but more importantly you can bind it to multiple UI things and they all track the same selection automatically.

There are two ways to activate the behavior – first is through the normal Blend framework -

 

<ListView ItemsSource="{Binding Names}" SelectionMode="Multiple" HorizontalAlignment="Stretch" Margin="20">
    ...            
    <Interactivity:Interaction.Behaviors>
       <Interactivity1:SynchronizedCollectionBehavior 
                  Collection="{Binding SelectedNames}" />
    </Interactivity:Interaction.Behaviors>
</ListView>

Here the ViewModel is exposing an ObservableCollection of names (called SelectedNames) to manage the selected items. The actual collection (also string names) is just Names:

public IReadOnlyList<string> Names { get; private set; }
public IList<string> SelectedNames { get; private set; }

public MainViewModel()
{
    Names = new List<string>
                {
                    "Alice", "Bob", "Carol", "David", "Edgar", "Frank",
                    "Georgia", "Hank", "India", "Jack", "Karen", "Larry",
                    "Mike", "Nate", "Oscar", "Peter", "Quix", "Russ", "Steve",
                    "Tonya", "Uma", "Violet", "Walter", "Xi", "Yvonne", "Zed"
                };
    SelectedNames = new ObservableCollection<string>();
   ...
}

The second activation mechanism is through a normal attached property –

<GridView ItemsSource="{Binding Names}" Grid.Column="1"  Grid.Row="1"
          SelectionMode="Extended" HorizontalAlignment="Stretch" Margin="20"
          Interactivity1:SynchronizedCollectionBehavior.IsEnabled="{Binding SelectedNames}">

This performs exactly the same action but allows it to be done as an attribute.

Here is a test program which shows connecting it up to multiple ItemsControls and even changing the selection styles between them.

SynchronizedList

Navigation and Persistence with MVVM in Windows Store Apps take #2

Earlier I posted on an updated version of MVVMHelpers with persistence support (see here). Paulo Quicoli, a long-time MVVMHelpers contributor, sent me a little code and a different way of managing navigation and persistence that he’s been using. I thought it was quite elegant and asked if I could include a version in the library, which he graciously agreed to.  The result will be in the next release of the library – but I thought I’d introduce it here.

The idea is to use a serializer on the ViewModel so that it is always passed as the parameter in navigation – in this case, as a string.  The code Paulo provided was a simple use of the DataContractJsonSerializer to turn an object into a Json string – which we can then pass through the normal navigation APIs so it gets captured into the navigation stack automatically.  In this scenario, we are doing ViewModel-first – we’ll create the ViewModel and then navigate to the view – passing the view model as the navigation parameter.

To accomplish this, two new classes were added into MVVMHelpers – both optional.  The first is the JulMar.Windows.Serialization.Json static class which looks like:

public static class Json
{
    ///
<summary> /// This method serializes an object or graph into a JSON string
 /// </summary>
    ///Instance to serialize
    /// String
    public static string Serialize(object instance);

    ///
<summary> /// This takes a JSON string and turns it into an object or graph.
 /// </summary>
    /// Type
    ///JSON string
    /// Object graph
    public static T Deserialize(string stream);

    ///
<summary> /// This takes a JSON string and turns it into an object or graph.
 /// </summary>
    ///Type
    ///JSON string
    /// Object graph
    public static object Deserialize(Type type, string stream);
}

This class is just a slim wrapper around the serializer to take an object and turn it into a string and vice-versa.  Note that the object must be serializable – which as of the last public release, the base ViewModel classes are.

The second class is an implementation of the IPageNavigator interface which supports the above serialization.  It does several things:

  1. It uses the NavigateTo method which takes a page key (or type) and single parameter, or a parameter and ViewModel (in which case, the parameter is ignored).
  2. It serializes the parameter/ViewModel into a string and passes that to the normal frame navigation API.
  3. When the page is navigated TO, the string is de-serialized back into a ViewModel and set as the DataContext.
  4. It properly handles suspension/resumption by saving/restoring the navigation stack and deserializing the ViewModel (if available) from the parameters back to the current page.
It’s easy to use, although is not the default navigation provider – so there is a setup step that needs to be performed in order to use this new page navigator.  Specifically, in the startup sequence of the application (typically, the Application constructor in App.xaml.cs) you will need to add the highlighted line to replace the default page navigator:
public App()
{
    ServiceLocator.Instance.Add(typeof(IPageNavigator), new AutoSerializingPageNavigator());

    this.InitializeComponent();
    this.Suspending += OnSuspending;
}

You still need to add the support to save/restore the state in your OnLaunched and Resuming events as well, this is the same code as the normal page navigator:

protected async override void OnLaunched(LaunchActivatedEventArgs args)
{
    IPageNavigator pageNavigator = ServiceLocator.Instance.Resolve();
    Frame rootFrame = Window.Current.Content as Frame;

    if (rootFrame == null)
    {
        rootFrame = new Frame();
        Window.Current.Content = rootFrame;

        if (args.PreviousExecutionState == ApplicationExecutionState.Terminated)
        {
            await pageNavigator.LoadAsync();
        }
    }
    ...
}

private async void OnSuspending(object sender, SuspendingEventArgs e)
{
    IPageNavigator pageNavigator = ServiceLocator.Instance.Resolve();
    var deferral = e.SuspendingOperation.GetDeferral();

    await pageNavigator.SaveAsync();

    deferral.Complete();
}

Next, the ViewModel’s must participate in this and be properly serializable – that means saving all the appropriate state by marking the properties with [DataMember] or with [IgnoreDataMember] based on whether you decorate the type with [DataContract] or not.  Also, remember that when you use [DataContract] the default constructor is not run – that means putting initialization logic into an [OnDeseralizing] method.  This is the primary reason I chose not to make this approach the default as it puts a bit of a burden onto the ViewModel – but once this is done, everything else is taken care of:

  • It will navigate and setup the DataContext automatically
  • It will properly save/restore the state on suspension/resumption

Here’s an example of a simple color view model:

[DataContract]
public sealed class ColorViewModel : ViewModel
{
    [DataMember]
    public string Color { get; set; }

    public IDelegateCommand GoBack { get; private set; }

    public ColorViewModel()
    {
        Initialize(new StreamingContext());
    }

    [OnDeserialized]
    void Initialize(StreamingContext context)
    {
        GoBack = new DelegateCommand(() => Resolve<IPageNavigator>().GoBack(), () => Resolve<IPageNavigator>().CanGoBack);
    }

    public ColorViewModel(string color) : this()
    {
        Color = color;
    }
}

And here’s the logic to navigate to this view model:

[DataContract]
public sealed class MainViewModel : ViewModel
{
    [Import]
    public IPageNavigator PageNavigator { get; set; }
    public IList<ColorViewModel> Colors { get; private set; }
    public IDelegateCommand SelectSpecificColor { get; private set; }

    public MainViewModel()
    {
        Initialize(new StreamingContext());
    }

    [OnDeserializing]
    void Initialize(StreamingContext context)
    {
        SelectSpecificColor = new DelegateCommand<ItemClickEventArgs>(OnSelectColor);
        Colors = new ObservableCollection<ColorViewModel>(
            typeof(Colors).GetTypeInfo().DeclaredProperties.Select(pn => new ColorViewModel(pn.Name)));
    }

    private void OnSelectColor(ItemClickEventArgs e)
    {
        PageNavigator.NavigateTo("OneColorView", e.ClickedItem);
    }
}

In addition, the Page and ViewModel can still participate in the INavigationAware interface – the new page navigator will still invoke it.  So this new capability allows for an alternative mechanism for managing page state and navigation – I expect to release it early next week! If you are interested in the full sample, check out the Source Code and look at the sample test project AutoSerializingNavigationTest.

MVVMHelpers.Metro updated

The Metro version of MVVMHelpers was updated yesterday with some key new features, one of them is some built-in state management for process lifetime and navigation. This is done through two interfaces – IPageNavigator and IStateManager.

You can use them independently, or together. By default, if you don’t assign one, the PageNavigator service will internally create a StateManager to store off page navigation state. You can supply your own IStateManager implementation by either overriding the framework (i.e. apply an [Export(typeof(IStateManager)] to a type) or by simply setting the StateManager property on the IPageNavigator itself. I’ll show that in a later blog post, but now let’s see how they work together. To give you an example, let’s build a sample app.

First, fire up Visual Studio 2012 on a Windows 8 box and create a new Blank App project. I called it “SampleBoxes”. Next, add references to MVVMHelpers and MEF through Nuget: Right click on references and select “Manage Nuget References”. Select “Online” and type “MVVMHelpers” in the search box:

Click “Install” to add the references. Next, modify the app.xaml.cs with code to use the new types – you can use the ServiceLocator to get the IPageNavigator interface.

protected override void OnLaunched(LaunchActivatedEventArgs args)
{
    IPageNavigator pageNavigator = ServiceLocator.Instance.Resolve();
    Frame rootFrame = Window.Current.Content as Frame;
    ...

In the same method, just a little further down, modify the code to create the frame, assign the content and then reload any persisted settings. This must be done after setting the frame as this method both loads the underlying state using an internal IStateManager and also restores the navigation stack if it is present. Note: since the call to LoadAsync is asynchronous, we will use an await here, make sure to mark the method itself as an async method

if (rootFrame == null)
{
    // Create a Frame to act as the navigation context and navigate to the first page
    Window.Current.Content = rootFrame = new Frame();

    // Reload our saved state
    if (args.PreviousExecutionState == ApplicationExecutionState.Terminated)
    {
        await pageNavigator.LoadAsync();
    }
}

Finally, in the OnSuspending event handler at the bottom of the app.xaml.cs file, locate the IPageNavigator and call SaveAsync to save off all the page state and navigation stack.

private async void OnSuspending(object sender, SuspendingEventArgs e)
{
    var deferral = e.SuspendingOperation.GetDeferral();

    IPageNavigator pageNavigator = ServiceLocator.Instance.Resolve();
    await pageNavigator.SaveAsync();

    deferral.Complete();
}

That’s all we need to add in order to save and restore our state. Now, how do we actually add things to the state manager itself? The key is the INavigationAware interface – if you implement this in either the View (Page classes) or ViewModel (DataContext of the Page) then it will automatically get called when the page navigation occurs, both from and to. The event args will have an IDictionarywhich you can use to store off any settings specific to that page or view model. Note that the dictionaries will be different for each (they are segregated so you do not have to worry about key overlaps).

Create a folder in the solution called “ViewModels” and add a new class into the folder called MainViewModel.cs, go ahead and derive from the ViewModel base class. Next, add a data class into the project – I’ve named it ColorBox and here’s the code:

public sealed class ColorBox
{
    public string Color { get; private set; }

    public ColorBox(Color color)
    {
        Color = color.ToString();
    }
}

This will act as our data. In the MainViewModel, add an IListand populate it with different colors. Here’s some sample code to use if you want to copy/paste:

public sealed class MainViewModel : ViewModel
{
    public IList Boxes { get; private set; }

    public MainViewModel()
    {
        Random RNG = new Random();
        Boxes = new List<ColorBox>(
                Enumerable.Range(0, 200).Select(n => new ColorBox(
                    Color.FromArgb(255, (byte)RNG.Next(255), (byte)RNG.Next(255), (byte)RNG.Next(255)))));
    }
}

Add an ExportViewModel attribute to the view model:

[ExportViewModel("MainViewModel")]
public sealed class MainViewModel : ViewModel
{
...
}

Now, open the MainPage.xaml file – add a property to the Page element to locate the view model:

<Page
    x:Class="SampleBoxes.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:SampleBoxes"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    xmlns:Mvvm="using:JulMar.Windows.Mvvm"
    Mvvm:ViewModelLocator.Key="MainViewModel">

Next, add a GridView to display the color boxes – just replace the Grid already present in the file with the following:

<Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
    <Grid.RowDefinitions>
        <RowDefinition Height="140" />
        <RowDefinition />
        <RowDefinition Height="50" />
    </Grid.RowDefinitions>
        
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="120" />
        <ColumnDefinition />
        <ColumnDefinition Width="50" />
    </Grid.ColumnDefinitions>
        
    <TextBlock Text="Colored Boxes" Style="{StaticResource PageHeaderTextStyle}" Grid.ColumnSpan="2" Grid.Column="1" />
        
    <GridView Grid.Row="1" Margin="10,9,10,11" Grid.Column="1" ItemsSource="{Binding Boxes}" IsItemClickEnabled="True">
        <GridView.ItemTemplate>
            <DataTemplate>
                <Rectangle Margin="5" Fill="{Binding Color}" Width="200" Height="200" />
            </DataTemplate>
        </GridView.ItemTemplate>
    </GridView>
</Grid>

Compile it and you will see design-time data – the ViewModelLocator works fine with the designer. It should run as well. Add a second Blank page into the project – name it BoxDetailsPage.xaml and use the following XAML:

<Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
    <Grid.RowDefinitions>
        <RowDefinition Height="140" />
        <RowDefinition />
        <RowDefinition Height="50" />
    </Grid.RowDefinitions>

    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="120" />
        <ColumnDefinition />
        <ColumnDefinition Width="50" />
    </Grid.ColumnDefinitions>

    <Button Style="{StaticResource BackButtonStyle}" Command="{Binding GoBack}" />
    <TextBlock Text="Box Details" Style="{StaticResource PageHeaderTextStyle}" Grid.ColumnSpan="2" Grid.Column="1" />

    <Rectangle Fill="{Binding Color}" Width="400" Height="400" Grid.Column="1" Grid.Row="1" />
</Grid>

Notice that the Back button is using a Command here to go backwards — you could also use the LayoutAwarePage.cs here as the IPageNavigator utilizes the normal Frame navigation under the covers so you can mix and match as necessary; but here I will show you how to use a ViewModel to drive it.

Add a new ViewModel into the source called BoxDetailsViewModel.cs – here’s the code:

public sealed class BoxDetailsViewModel : ViewModel
{
    private string _color;
    public string Color
    {
        get { return _color; }
        set { SetPropertyValue(ref _color, value); }
    }

    [Import]
    public IPageNavigator PageNavigator { get; set; }

    public IDelegateCommand GoBack { get; private set; }

    public BoxDetailsViewModel()
    {
        GoBack = new DelegateCommand(() => PageNavigator.GoBack(), () => PageNavigator.CanGoBack);
    }
}

Notice we are importing the IPageNavigator rather than using the ServiceLocator. This works because the base ViewModel class makes sure to satisfy imports – if you derive from SimpleViewModel (which just has the INotifyPropertyChanged implementation) then you would need to do this step yourself. Here you can see the first usage of the page navigator to do true navigation – we are using the GoBack() method and CanGoBack property to implement our command.

Now, let’s integrate this page into our system. Back in the MainViewModel, add the following code:

[ExportViewModel("MainViewModel")]
public sealed class MainViewModel : ViewModel
{
    public IList Boxes { get; private set; }

    [Import]
    public IPageNavigator PageNavigator { get; set; }

    public IDelegateCommand ShowDetails { get; private set; }

    public MainViewModel()
    {
        Random RNG = new Random();
        Boxes = new List<ColorBox>(
                Enumerable.Range(0, 200).Select(n => new ColorBox(
                    Color.FromArgb(255, (byte)RNG.Next(255), (byte)RNG.Next(255), (byte)RNG.Next(255)))));

        ShowDetails = new DelegateCommand<ItemClickEventArgs>(OnShowDetails);
    }

    private void OnShowDetails(ItemClickEventArgs e)
    {
        ColorBox box = (ColorBox) e.ClickedItem;
        PageNavigator.NavigateTo("BoxDetailsPage", box.Color, new BoxDetailsViewModel() { Color = box.Color });
    }
}

Next, open the MainPage.xaml file and modify the GridView with an EventTrigger behavior for the ItemClick event to execute our new command through an InvokeCommand behavior. By default if you do not supply a CommandParameter on the InvokeCommand behavior it will pass the EventArgs (an ItemClickEventArgs in this case) which is exactly what we’ve coded the MainViewModel to receive on the command.

Here are the relevant changes, first add the namespace to get to the two behavior classes:

<Page
    x:Class="SampleBoxes.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:SampleBoxes"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    xmlns:Mvvm="using:JulMar.Windows.Mvvm" 
    xmlns:Interactivity="using:System.Windows.Interactivity"
    xmlns:Interactivity1="using:JulMar.Windows.Interactivity" 
    Mvvm:ViewModelLocator.Key="MainViewModel">

Next, add the triggers collection onto the GridView:

<GridView Grid.Row="1" Margin="10,9,10,11" Grid.Column="1" ItemsSource="{Binding Boxes}" IsItemClickEnabled="True">
    <Interactivity:Interaction.Triggers>
        <Interactivity:EventTrigger EventName="ItemClick">
            <Interactivity1:InvokeCommand Command="{Binding ShowDetails}" />
        </Interactivity:EventTrigger>
    </Interactivity:Interaction.Triggers>

Look back at the MainViewModel – specifically the ShowDetails handler. Notice it is using the PageNavigator (imported using MEF) and navigating to the “BoxDetailsPage” which is just a string. The normal Frame navigation uses System.Type objects which ties you to the specific classes. I decided instead to use strings – which is how MVVMHelpers has always tied things together; this can be fragile if you don’t manage it properly but it provides a lot more freedom when connecting views and viewmodels together and when navigating from VMs to views. The question is, “How does the navigation service know which page this corresponds to?” The answer is, a custom attribute named ExportPage (although you can also register it directly with the IPageNavigator service if you want to do it in code).

Open the BoxDetailsPage.xaml.cs file and add the following to the class declaration:

[ExportPage("BoxDetailsPage", typeof(BoxDetailsPage))]
public sealed partial class BoxDetailsPage : Page
{

This will export the page to the navigation service and make it easily found. You can do the same for the MainPage and then use the page navigation service in App.xaml.cs to get to the first page if you like. If you run the app and then suspend/terminate you will see the navigation stack is preserved, but your VM is not. There are two ways to manage this issue.

1) Serialize the DataContext for the view
2) Add a ViewModelLocator onto the view to create a BoxDetailsViewModel (replaced when we navigate, but used during initialization from persistence) and then have the ViewModel load/save state.

I prefer the second, but the first can be useful – it has several steps to it.

1) Make the BoxDetailsViewModel serializable. This means adding [DataContract] and [DataMamber] attributes as well as an OnDeserialized handler to re-hookup the command(s) properly and recompose via MEF since the constructor isn’t invoked in this case.

[DataContract]
public sealed class BoxDetailsViewModel : ViewModel
{
    private string _color;
    [DataMember]
    public string Color
    {
        get { return _color; }
        set { SetPropertyValue(ref _color, value); }
    }

    [Import]
    public IPageNavigator PageNavigator { get; set; }

    public IDelegateCommand GoBack { get; private set; }

    [OnDeserialized]
    public void OnDeserializing(StreamingContext context)
    {
        Initialize(); // force refresh of MEF
        GoBack = new DelegateCommand(() => PageNavigator.GoBack(), () => PageNavigator.CanGoBack);
    }

    public BoxDetailsViewModel()
    {
        GoBack = new DelegateCommand(() => PageNavigator.GoBack(), () => PageNavigator.CanGoBack);
    }
}

2) Implement the INavigationAware interface on the BoxDetailsPage.xaml.cs implementation and save/restore the DataContext.

[ExportPage("BoxDetailsPage", typeof(BoxDetailsPage))]
public sealed partial class BoxDetailsPage : Page, INavigationAware
{
    public void OnNavigatedTo(NavigatedToEventArgs e)
    {
        if (DataContext == null)
        {
            if (e.State != null && e.State.ContainsKey("DataContext"))
                DataContext = e.State["DataContext"];
        }
    }

    public void OnNavigatingFrom(NavigatingFromEventArgs e)
    {
        if (e.IsSuspending)
        {
            e.State["DataContext"] = this.DataContext; // save data context
        }
    }
}

3) Add the BoxDetailsViewModel as a known type in app.xaml.cs.

protected async override void OnLaunched(LaunchActivatedEventArgs args)
{
    IPageNavigator pageNavigator = ServiceLocator.Instance.Resolve<IPageNavigator>();
    pageNavigator.StateManager.KnownTypes.Add(typeof(BoxDetailsViewModel));

Adding those changes will then properly save/restore the ViewModel (from the DataContext) and navigation stack on suspend/resume. As I mentioned, that’s not my primary approach. Instead, I tend to export the ViewModel and wire it up with a locator in the XAML for each Page. Then I’ll pass the specific page I want when I navigate to it (the last parameter is the DataContext). This is set after the page is created so it will replace the ViewModelLocator version during initial navigation. Then, I’ll have the ViewModel persist it’s properties so that when the navigation stack is restored, it will be created (by the VMLocator) and then load it’s properties.

To do this, remove all the changes from above, and then wire up the ViewModelLocator to the BoxDetaislPage.xaml, export the BoxDetailsViewModel and then add ViewModelState attributes to each of the public properties you need to persist – in this case, just the Color property. Alternatively, you can also implement INavigationAware as we did above in the Page class and it will be called during navigation to save/restore state – this includes during suspend/resume.

So, here’s the ViewModel decorated:

[ExportViewModel("BoxDetailsVM")]
public sealed class BoxDetailsViewModel : ViewModel
{
    private string _color;
    [ViewModelState]
    public string Color
    {
        get { return _color; }
        set { SetPropertyValue(ref _color, value); }
    }

    [Import]
    public IPageNavigator PageNavigator { get; set; }
    public IDelegateCommand GoBack { get; private set; }

    public BoxDetailsViewModel()
    {
        GoBack = new DelegateCommand(() => PageNavigator.GoBack(), () => PageNavigator.CanGoBack);
    }
}

There is no need to manage serialization of this type since we aren’t storing the type itself – just the color string. This will produce the same result as before.

The sample project is here.