(X) Hide this
    • Login
    • Join
      • Generate New Image
        By clicking 'Register' you accept the terms of use .

WinRT Business Apps with Prism: Commands, Dependencies, and Navigation

(3 votes)
Brian Noyes
>
Brian Noyes
Joined Jun 10, 2010
Articles:   19
Comments:   117
More Articles
12 comments   /   posted on Mar 11, 2013

Tweet
 

UPDATE: This article was updated because the name of the guidance changed from it’s code name “Kona” to “Prism for Windows Runtime” for release. The content has not really changed since the original article, just updates for changes in the name of the guidance and for the change in code namespaces.

This is part 2 in the series WinRT Business Apps with Prism.

Introduction

In Part 1 of this series I covered the background of what Prism for Windows Runtime is and got you started building an app reusing the Microsoft.Practices.Prism.StoreApps project that contains the reusable code library of Prism. The sample application I put together in Part 1 was not very impressive, after seven steps, you had nothing more than a “Hello World” kind of application. But what may not have been apparent is that those steps let you lay down the foundation on which you could build a big, complex Windows Store business application with Prism, using the MVVM pattern, integrating with the navigation system, handling state management in the face of suspend/terminate easily and more.

In this article I will continue to build out the application a little farther, showing you how you can manage view model dependencies, how to handle commands from the UI in your view models, and how to let the view model participate in navigation as well as having it command navigation.

The starting point for the code in this article is the completed code from Part 1, which you can download here.

Step 1: Add a navigation control to the MainPage

I could just be totally crude and throw a button on the main page, but in a Windows Store application, top level navigation is usually presented by showing items in a GridView and allowing the user to click on those items to make things happen. So I will add a GridView to the main content area of the MainPage and add a single item to it for now, which will be the moral equivalent of a big fat navigation button for the user.

So I added the following to the outer Grid control that lays out the page with two rows – one for the header and one for the main content:

   1: <GridView Margin="120,10,0,0"
   2:             Grid.Row="1"
   3:             SelectionMode="None"
   4:             ItemsSource="{Binding NavCommands}">
   5:     <GridView.ItemTemplate>
   6:         <DataTemplate>
   7:             <Grid Height="500"
   8:                     Width="250"
   9:                     Background="#33CC33">
  10:                 <StackPanel VerticalAlignment="Center">
  11:                     <TextBlock Text="{Binding}"
  12:                                 Style="{StaticResource SubheaderTextStyle}"
  13:                                 HorizontalAlignment="Center" />
  14:                 </StackPanel>
  15:             </Grid>
  16:         </DataTemplate>
  17:     </GridView.ItemTemplate>
  18: </GridView>

Notice that I am expecting to expose a collection of strings I will name NavCommands from my view model. The ItemTemplate is set up to display those as big tall rectangles, with the display text in the rectangle as one string in the collection of NavCommands names.

Step 2: Add an Attached Behavior to hook up a command on ItemClick

Now the only trick is that I need a way to cause interaction with the GridView items – specifically clicking on an item – to dispatch a call into my view model. The GridView does not raise commands, which are a common way to communicate between a view and a view model. However, it does have an ItemClick event which you can enable with a flag called IsItemClickEnabled. A common approach in MVVM to pass an event from the UI to the view model is to use a behavior. to fire a command when the event fires. I wrote an article on my blog that explains what an Attached Behavior is and compares it to a simple attached property and to Blend Behaviors. Attached Behaviors are the only kind of behavior supported out of the box.

The code below shows an Attached Behavior class that can be used with the GridView (or a ListView since they share a common base class ListViewBase where the ItemClick event is defined).

   1: public static class ItemClickToCommandBehavior
   2: {
   3:     #region Command Attached Property
   4:     public static ICommand GetCommand(DependencyObject obj)
   5:     {
   6:         return (ICommand)obj.GetValue(CommandProperty);
   7:     }
   8:  
   9:     public static void SetCommand(DependencyObject obj, ICommand value)
  10:     {
  11:         obj.SetValue(CommandProperty, value);
  12:     }
  13:  
  14:     public static readonly DependencyProperty CommandProperty =
  15:         DependencyProperty.RegisterAttached("Command", typeof(ICommand), 
  16:             typeof(ItemClickToCommandBehavior),
  17:             new PropertyMetadata(null, OnCommandChanged));
  18:     #endregion
  19:  
  20:     #region Behavior implementation
  21:     private static void OnCommandChanged(DependencyObject d, 
  22:         DependencyPropertyChangedEventArgs e)
  23:     {
  24:         ListViewBase lvb = d as ListViewBase;
  25:         if (lvb == null) return;
  26:  
  27:         lvb.ItemClick += OnClick;
  28:     }
  29:  
  30:     private static void OnClick(object sender, ItemClickEventArgs e)
  31:     {
  32:         ListViewBase lvb = sender as ListViewBase;
  33:         ICommand cmd = lvb.GetValue(ItemClickToCommandBehavior.CommandProperty) as ICommand;
  34:         if (cmd != null && cmd.CanExecute(e.ClickedItem))
  35:             cmd.Execute(e.ClickedItem);
  36:     }
  37:     #endregion

You can see that what the behavior does is to hook up to the ItemClick event when the Command attached property gets set. It expects that the property will be set through a binding to point to an ICommand object in the view model. When the ItemClick event fires, it gets a reference to the bound command off the GridView element and fires the command, passing the clicked item as a command parameter. This allows the view model to do all the handling of that interaction.

To use this behavior, I add a setter for the Command attached property to the GridView like so, and enable IsItemClickEnabled:

   1: <GridView Margin="120,10,0,0"
   2:           Grid.Row="1"
   3:           SelectionMode="None"
   4:           ItemsSource="{Binding NavCommands}"
   5:           beh:ItemClickToCommandBehavior.Command="{Binding NavCommand}"
   6:           IsItemClickEnabled="True">

 

Step 3: Flesh out the MainPageViewModel nav commands

First I need to expose a property named NavCommands from the MainPageViewModel that is a collection of strings, one for each big fat navigation item we want to present in the GridView. For now that will just be one called “Add Sale”. In addition (based on the binding shown on the Command attached property) I need to expose a command property called NavCommand. The view model now looks like this:

   1: public class MainPageViewModel : ViewModel
   2: {
   3:     public MainPageViewModel()
   4:     {
   5:         NavCommands = new ObservableCollection<string>
   6:         {
   7:             "Add Sale"
   8:         };
   9:         NavCommand = new DelegateCommand<string>(OnNavCommand);
  10:     }
  11:  
  12:     public ObservableCollection<string> NavCommands { get; set; }
  13:     public DelegateCommand<string> NavCommand { get; set; }
  14:  
  15:     private void OnNavCommand(string navCommand)
  16:     {
  17:             
  18:     }
  19: }

At this point you should be able to set a breakpoint in the OnNavCommand method, run, click the Add Sale item in the grid and see that the command handler gets invoked.

Step 4a: Change to a Factory Method for ViewModel construction

In order to cause navigation to happen in the view model, it is going to need access to a NavigationService that gets set up in the MvvmAppBase class. That means we need to be able to inject that into the view model as a dependency. There are two ways to do that in Prism for Windows Runtime. The first is that you can set up a factory method for the view model type in the ViewModelLocator and write your own construction code for the view model, passing in whatever dependencies it needs. This is called manual dependency injection. The other is to use a dependency injection container, which I will also show in a later step.

So first I declare the dependency that the view model is going to take on:

   1: public class MainPageViewModel : ViewModel
   2: {
   3:     private readonly INavigationService _NavService;
   4:     public MainPageViewModel(INavigationService navService)
   5:     {
   6:         _NavService = navService;
   7:         ...
   8:     }
   9:     ...
  10: }

Then I go to the App.xaml.cs code behind file and add an override for the OnInitialize method from the base class. In that method, I call the Register method on the ViewModelLocator to set up a factory method (in the form of a Func<object> lambda expression) for the MainPageViewModel type:

   1: protected override void OnInitialize(IActivatedEventArgs args)
   2: {
   3:     base.OnInitialize(args);
   4:     ViewModelLocator.Register(typeof(MainPage).ToString(),
   5:         () => new MainPageViewModel(NavigationService));
   6: }

Now whenever the ViewModel locator View-to-ViewModel type mapping happens, it will then look to see if there is a registered factory method for the ViewModel type, and if so will use it to construct the view model instead of using the default logic that just constructs the ViewModel with a default constructor. You can see in the snippet above that this allows me to pass in the NavigationService from the MvvmAppBase class as the implementation of INavigationService that the view model is depending on.

Step 4b: Set up a Dependency Injection Container for the App

As an alternative to the approach shown in step 4a, if you are already comfortable with the concepts and usage of a dependency injection (or Inversion of Control – IOC) container, then you can use that instead of the manual dependency injection approach of using the factory methods. If you use a container like Unity that supports constructor injection, then you would define the view model dependency exactly the same as shown in the first code snippet of Step 4a.

Next I pull in Unity as my container of choice for known dependencies in WinRT apps. At the time of this writing, Unity for WinRT has not yet been released, but it is available in pre-release form as a NuGet package. So I right click on my project in Solution Explorer, select Manage NuGet Packages, make sure “Include Prerelease” is selected at the top of the NuGet dialog, and do a search for Unity.

Figure1

After installing that NuGet package, The modifications to App.xaml.cs look like this:

   1: sealed partial class App : MvvmAppBase
   2: {
   3:     IUnityContainer _Container = new UnityContainer();
   4:     public App()
   5:     {
   6:         this.InitializeComponent();
   7:     }
   8:  
   9:     protected override void OnLaunchApplication(LaunchActivatedEventArgs args)
  10:     {
  11:         NavigationService.Navigate("Main", null);
  12:     }
  13:  
  14:     protected override void OnInitialize(IActivatedEventArgs args)
  15:     {
  16:         base.OnInitialize(args);
  17:         _Container.RegisterInstance<INavigationService>(NavigationService);
  18:         ViewModelLocator.SetDefaultViewModelFactory(
  19:             (viewModelType) => _Container.Resolve(viewModelType));
  20:     }
  21: }

Notice on line 3 I construct the container and keep it around as a singleton for the life of the application just by declaring it as a member variable on the App class. On lines 14-20 I override the OnInitialize method from the base class, set up a registration in the container for the INavigationService interface, and then change the default factory for the ViewModelLocator to construct all view models through the container. By doing so, the container will automatically try to resolve and pass in any dependencies indicated in the view model constructor.

Step 5: Navigate – To AddSalePage and Beyond!

Now that the view model has access to the navigation service, it can cause navigation to happen when it sees fit. Add the following code to the MainPageViewModel.OnNavCommand method:

   1: private void OnNavCommand(string navCommand)
   2: {
   3:     _NavService.Navigate("AddSale", null);
   4: }

As mentioned in Part 1 of this series, navigation is done based on string tokens for the pages, where the “Page” part of the page name is left off by convention. That convention is overridable in the MvvmAppBase.GetPageNameToTypeResolver method. If you have followed along to this point, you should be able to fire up the app at this point, click on the Add Sale tile in the MainPage and land on the AddSalePage (which is empty at this point).

Once you have landed on the AddSalePage, it has a Back button in the upper left corner, but it is not hooked up to anything yet. To let the view model be in charge of all navigation logic, the AddSalePageViewModel needs access to the NavigationService as well. If you added a container in Step 4b, then you don’t have to do anything other than declare the dependency on INavigationService as a constructor argument. If you stuck with the container-less approach in Step 4a, then you will need to register another factory method for AddSalePageViewModel to the OnInitialize method:

   1: ViewModelLocator.Register(typeof(AddSalePageViewModel).ToString(),
   2:     ()=> new AddSalePageViewModel(NavigationService));

To set up the view model to take in the INavigationService and handle GoBack commands, it would look like this:

   1: public class AddSalePageViewModel : ViewModel
   2: {
   3:     private readonly INavigationService _NavService;
   4:     public AddSalePageViewModel(INavigationService navService)
   5:     {
   6:         _NavService = navService;
   7:         GoBackCommand = new DelegateCommand(
   8:             () => _NavService.GoBack(),
   9:             () => _NavService.CanGoBack());
  10:     }
  11:  
  12:     public DelegateCommand GoBackCommand { get; set; }
  13: }

Now a simple modification to the backButton in AddSalePage gets it all lit up and ready to use:

   1: <Button x:Name="backButton"
   2:         Command="{Binding GoBackCommand}"
   3:         Style="{StaticResource BackButtonStyle}" />

 

Step 6: Add navigation from an AppBar

In addition to the GridView navigation on the home page, you might also want to let people navigate to specific pages regardless of where they are in the application. To do that you would include a top AppBar on each page with navigation buttons for the places in your application you want to allow them to navigate to from there. For example, I could add the following to my MainPage:

   1: <Page.Resources>
   2:     <Style x:Key="AddSaleAppBarButtonStyle"
   3:            TargetType="ButtonBase"
   4:            BasedOn="{StaticResource AppBarButtonStyle}">
   5:         <Setter Property="AutomationProperties.AutomationId"
   6:                 Value="AddAppBarButton" />
   7:         <Setter Property="AutomationProperties.Name"
   8:                 Value="Add Sale" />
   9:         <Setter Property="Content"
  10:                 Value="" />
  11:     </Style>
  12: </Page.Resources>
  13: <Page.TopAppBar>
  14:     <AppBar>
  15:         <StackPanel Orientation="Horizontal">
  16:             <Button Command="{Binding AddSaleCommand}"
  17:                     Style="{StaticResource AddSaleAppBarButtonStyle}" />
  18:         </StackPanel>
  19:     </AppBar>
  20: </Page.TopAppBar>

Then I would just add an AddSaleCommand to the view model:

   1: public DelegateCommand AddSaleCommand { get; set; }

And wire up its handler to use the navigation service to cause the navigation.

   1: AddSaleCommand = new DelegateCommand(() => _NavService.Navigate("AddSale", null));

Obviously in a complex app with many pages, this would be a bad idea to repeat across many pages if you wanted to be able to navigate to the AddSalePage from anywhere in the application. To avoid that, what I would do is wrap it up in a user control that I could add to any page’s app bar declaratively in the XAML.

 

Step 7: Let the ViewModel know when it is navigated to

Sometimes you might need to pass arguments between view models when navigation happens. For example if you were presenting a list of items to select in one view, and the next view you navigated to was going to present that item for editing or other interaction. You can see in the navigation service Navigate method in the first snippet of Step 5 that there is a second parameter that lets you pass some context. In that step I passed null, but that won’t always be sufficient. The way you can receive that parameter in the view model that is being navigated to is to override a method from the ViewModel base class called OnNavigatedTo. Additionally if you need to do some clean up when the user navigates away from a view, there is a corresponding OnNavigatedFrom method as well.

If I add an override for OnNavigatedTo to the AddSalePageViewModel, the signature looks like this:

   1: public override void OnNavigatedTo(object navigationParameter, 
   2:     NavigationMode navigationMode, Dictionary<string, object> viewState)
   3: {
   4:     base.OnNavigatedTo(navigationParameter, navigationMode, viewState);
   5: }

The parameters to the method include the navigation parameter that can be passed from the view model that caused the navigation, a NavigationMode parameter, and a viewState parameter. The NavigationMode parameter is part of WinRT and tells you whether you arrived at your destination through a “New”, “Forward”, “Back”, or “Refresh” navigation. A call to INavigationService.Navigate results in a “New” navigation – meaning it will be another step in the navigation stack that is maintained by WinRT. INavigationService.GoBack will result in the “Back” value. The other two are exposed by the Frame class in WinRT when you use the GoForward or Refresh navigation methods. We chose not to implement those in the FrameNavigationService class in Prism since they are fairly uncommon to expose in a Windows Store app as explicit navigation controls, but you could easily extend the FrameNavgationService to add those if it made sense for your app, and their implementation would just be to wrap the Frame.GoForward or Frame.Refresh methods.

The viewState argument is a dictionary that you can read and write values into that will participate in suspend/terminate state persistence – which will be the subject of my next article in the series, so just ignore that parameter for now.

Summary

In this article, I showed you how you can use the Prism DelegateCommand class to handle communications from the view down into the view model, with the assistance of a behavior to map GridView.ItemClick events into command invocations. You also saw how to wire up dependencies in your view models either through manual dependency injection with a factory method supplied to the ViewModelLocator (Step 4a) or by using an IOC/DI container (Step 4b). You saw how to use the INavigationService to control navigation from the view models, as well as how to let the view models know when they are being navigated to or from.

In the next article, I will show how we can manage application state in the face of suspend/terminate/resume – which is an inherent part of WinRT application lifecycle.

About the Author

Brian Noyes is CTO of Solliance (www.solliance.net), a software development company offering Architecture as a Service, end-to-end product development, technology consulting and training. Brian is a Microsoft Regional Director and MVP, Pluralsight author, and speaker at conferences worldwide.  Brian worked directly on the Prism team with Microsoft patterns and practices. Brian got started programming as a hobby while flying F-14 Tomcats in the U.S. Navy, later turning his passion for code into his current career. You can contact Brian through his blog at http://briannoyes.net/ or on Twitter @briannoyes.


Subscribe

Comments

  • SureshPokkuluri

    Re: Windows Store LOB Apps with Kona: Commands, Dependencies, and Navigation


    posted by SureshPokkuluri on Mar 20, 2013 18:10
    Thanks for great post!
  • jschaffe

    Re: Windows Store LOB Apps with Kona: Commands, Dependencies, and Navigation


    posted by jschaffe on Apr 03, 2013 18:18
    Looking forward to the next article in the series.  Any idea on a publish date?
  • brian.noyes

    Re: Windows Store LOB Apps with Kona: Commands, Dependencies, and Navigation


    posted by brian.noyes on Apr 03, 2013 18:42

    Holding off a bit because there may be some renaming of the namespaces that I don't want to have to chase with updates. Hopefully by the end of next week.

  • jschaffe

    Re: Windows Store LOB Apps with Kona: Commands, Dependencies, and Navigation


    posted by jschaffe on Apr 03, 2013 21:12
    Looking forward to the next article in the series.  Any idea on a publish date?
  • brian.noyes

    Re: Windows Store LOB Apps with Kona: Commands, Dependencies, and Navigation


    posted by brian.noyes on Apr 03, 2013 21:43

    see above, answered this morning

  • cong

    Re: Windows Store LOB Apps with Kona: Commands, Dependencies, and Navigation


    posted by cong on Apr 05, 2013 05:20
    I thanks to you for series, I hope next article come soon,thanks very much,I come form Vietnam.
  • kkumar

    Re: WinRT Business Apps with Prism: Commands, Dependencies, and Navigation


    posted by kkumar on Apr 19, 2013 03:37

    Brilliant and accessible article series. Fan of your work both here and on Pluralsight.

    Would love to see the remaining articles drop here quickly since your writing is way more effective than MSDN's or P&P's. Are you also planning a Pluralsight series on Kona?

  • brian.noyes

    Re: WinRT Business Apps with Prism: Commands, Dependencies, and Navigation


    posted by brian.noyes on Apr 19, 2013 14:31

    Hi kkumar,

    Thanks much, I'm glad you are enjoying it. I am in fact half way through putting together a Pluralsight course on Kona/Prism. I had to pause for a few weeks because of the pending renaming, but now that the rename is official and the guidance will be called Prism for Windows Runtime, I am getting started re-recording the modules I had already done today.

    I also have a heavy consulting load, so that course realistically won't be out until mid-late May. I'll be getting the next article in this series out by mid-next week, and then continuing to put out an additional 3-4 more about every two weeks.

    Sorry I can't produce them faster, just have to juggle multiple commitments as an author and consultant.

  • kkumar

    Re: WinRT Business Apps with Prism: Commands, Dependencies, and Navigation


    posted by kkumar on Apr 28, 2013 21:06

    Thank you for your response, Brian.

  • brian.noyes

    Re: WinRT Business Apps with Prism: Commands, Dependencies, and Navigation


    posted by brian.noyes on Apr 28, 2013 23:44

    FYI - third article is done and should go live in the next couple days.

  • kkumar

    Re: WinRT Business Apps with Prism: Commands, Dependencies, and Navigation


    posted by kkumar on Apr 29, 2013 00:03

    Awesome, thanks a lot, Brian. Looking forward...

    Also, just wanted to point out that in this article where you setup manual DI, you are using the ViewModel type name, whereas I think it should be the View type name?

    so, should it be 

    ViewModelLocator.Register(typeof(MainPage).ToString(), () => new MainPageViewModel(NavigationService));

    instead of 

    ViewModelLocator.Register(typeof(MainPageViewModel).ToString(), () => new MainPageViewModel(NavigationService));?

    Please clarify.

  • brian.noyes

    Re: WinRT Business Apps with Prism: Commands, Dependencies, and Navigation


    posted by brian.noyes on Apr 29, 2013 13:48

    Absolutely correct, good catch. Will get that updated. It is the view type name you want to pass to the Register method because it basically takes care of both the view model type resolution and the creation of the instance in one step that you fully control.

Add Comment

Login to comment:
  *      *       

From this series