14 June 2012

NavigationService for WinRT

"If I have been able to see further than others, it is because I have stood on the shoulders of giants."
 Isaac Newton

It was only 1.5 years ago, but it seems already a long time ago that Laurent Bugnion described a “view service” for Windows Phone navigation, commonly know as the NavigationService. I’ve incorporated this code in my #wp7nl library on codeplex and have been using it happily ever since (and a lot of other people I know have been doing so as well), Time goes on, along came Windows 8 and WinRT and Metro Style Apps.

Laurent ported his MVVMLight framework to WinRT as well. But the NavigationService never has been a core part of MVVMLight (therefore I put it in the #wp7nl library) and porting it to WinRT as well proved to be a bit of a hassle. Some of its premises where no longer valid – most notable the way to retrieve the main application frame by accessing Application.Current.RootVisual, which does not work in WinRT. I messed around a little with the code, did not get anywhere, and left it there. So I was glad I saw my fellow Windows Phone Development MVP Matteo Pagani tweet he got a NavigationService to work. He was kind enough to mail me the code. He basically copied the code from Windows Phone and made the apps rootFrame, as created in the App.xaml.cs, publicly available. A neat trick, with as only drawback that you have to copy the code to every app. As things goes, it’s usually easier to see someone else’s idea and improve it, than think it up out of the blue. I toyed around with it and managed to get rid of the need to copy the code, so I could put it in a library.

And here it is, an improved version of a NavigationService for WinRT, based upon Matteo’s code based upon Laurent’s code ;-)

First of all, the NavigationService’s interface:

using System;
using Windows.UI.Xaml.Navigation;

namespace Win8nl.Services
{
  public interface INavigationService
  {
    event NavigatingCancelEventHandler Navigating;
    void Navigate(Type type);
    void Navigate(Type type, object parameter);
    void Navigate(string type);
    void Navigate(string type, object parameter);
    void GoBack();
  }
}

This quite looks like the old interface, with some notable exceptions. First of all, the NavigateTo method is renamed Navigate, to mimic the method name used in WinRT itself. And it has not one but four overloads. The first two are pretty logical – WinRT does not use an uri for navigation, but an actual Type object, making strongly-typed navigation possible. And it supports an overload for a parameter object, as well. The two other methods, with the string type object… well see for yourself below, in the implementation of the service:

using System;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Navigation;

namespace Win8nl.Services
{
  public class NavigationService : INavigationService
  {
  public NavigationService(Frame mainFrame)
  {
    _mainFrame = mainFrame;
  }

  private Frame _mainFrame;
  
  public event NavigatingCancelEventHandler Navigating;

  public void Navigate(Type type)
  {
    _mainFrame.Navigate(type);
  }

  public void Navigate(Type type, object parameter)
  {
    _mainFrame.Navigate(type, parameter);
  }

  public void Navigate(string type, object parameter)
  {
    _mainFrame.Navigate(Type.GetType(type), parameter);
  }

  public void Navigate(string type)
  {
    _mainFrame.Navigate(Type.GetType(type));
  }

  public void GoBack()
  {
    if (_mainFrame.CanGoBack)
    {
      _mainFrame.GoBack();
    }
  }  
}

So the string type methods allow you to specify the page to navigate by string. Before you all think I really lost my marbles this time, re-introducing weakly typed navigation just as the Windows team introduced strongly typed: I specifically added this as to allow navigation to be initiated from a ViewModel contained in an assembly not containing the XAML code. This also enables you to test code as well. This is the way I built my solutions for Windows Phone, and I intend to go on this way in Windows 8. ;-)

Anyway, there’s a thing missing in this code: the EnsureMainFrame that looked up the root page, for which Matteo used a public property. I completely removed it, and added a constructor accepting that root page. By creating that constructor I deleted the default constructor, so registering the NavigationService at he SimpleIoc container shipped with MVVMLight in the App.xaml.cs, like this:

SimpleIoc.Default.Register<INavigationService, Wp7nl.Utilities.NavigationService>();

as we did in Windows Phone code, won’t work anymore. Fortunately, SimpleIoc also supports factory methods to create an instance. So now you go to the App.xaml.cs of your Windows 8 app, find the OnLaunched method and just behind this line:

var rootFrame = new Frame();

you add this piece of code:

SimpleIoc.Default.Register<INavigationService>(() => 
                                   {return new NavigationService(rootFrame);});

Now if your XAML code and ViewModels are all in one assembly, you can call the NavigationService to navigate to the main page this:

SimpleIoc.Default.GetInstance<INavigationService>().Navigate(typeof(MainPage));

or if you are a bit stubborn like me and would like to separate ViewModels and views, you can use the string overload to specify the full qualified name and for instance use it in a ViewModel command like this

public ICommand TestNavigatieCommand
{
   get
   {
     return new RelayCommand(() => 
       SimpleIoc.Default.GetInstance<INavigationService>().Navigate("MyApp.MainPage,MyApp"));
   }
}

And there you have, a fully functional NavigationService for Windows 8. Code can be downloaded here. I will soon incorporate it in the provisional port of the #wp7nl library called win8nl.

Thanks to Laurent and Matteo for being my trailblazers ;-)

12 comments:

stephan said...

The implementation is ok, but my doubt is how i can persist the view while terminating the application.

Joost van Schaik said...

@Stephan - what doe you mean? Storing the exact page you were viewing when you terminated the application and make sure you start there again when you relaunch the app?

Unknown said...

Hello,

I have main view with list of items (item is instance of model Test) and second view for editing each of them.

If I want to use Navigate with parameter: Navigate(typeof(SecondView), SelectedItem), how can I get SelectedItem in second view/viewModel?

And if I want to change properties of selected item in second view (edit in textboxes and click on the save button), how can I show changes in main view?

Thank you very much

Joost van Schaik said...

@Jakub,

I would store the selecteditem in a property of the same viewmodel as the list - and then bind that same property to your detail page. That then being the same object, property changes should immediately be reflected in both master and detail screen. In other words, don't use the navigation properties, use a view model property.

Unknown said...

@Joost,

I would like to save changes after click on the save button (not immediately).

My idea - in the constructor of view model (detail page) get SelectedItem from MainViewModel and fill in text boxes.

Is this good solution?
SimpleIoc.Default.GetInstance().SelectedItem;

Joost van Schaik said...

@Jacub,

Possibly. Haven't tried that approach - I always go with the 'whatever work as long as you prevent codebehind hacks' way. It seems like a good solution but I haven't tried it myself. If it works, by all means blog about it and I will link from here ;-)

Unknown said...

This is great, but who do you pass the parameter to the viewmodel? I can't think of a way to do it without using a codebehind hack?

Ta

Ross

Joost van Schaik said...

@Ross,

You can either try to use EventToCommandBebavior to poke the selected object into your view model, or you can bind the selected object directly to the view model. Fooling around with the Messenger to transfer and object from one viewmodel to another might help too. MVVMLight even sports and overload of RaisePropertyChanged with a boolean that broadcasts property changes over the Messenger automatically.

Shashank Bisen said...

Hi Joost,

I am new to MVVM, i was reading through your blog and trying to implement it as below step.

1. Add NuGet package Win8nl utilities.

2. Add a button to my xaml
Button Command="{Binding Navigate}" Margin="3" Content="Navigate"

3. Created ICommand property
private ICommand _navigate;

public ICommand Navigate
{
get
{
if (_navigate == null)
_navigate = new RelayCommand(NavigatePage);
return _navigate;
}
}
4. In the Navigate method i am trying to navigate to MainPage
public void NavigatePage()
{
SimpleIoc.Default.GetInstance().Navigate(typeof(MainPage));
}
but on button click i am getting the following error in Navigate method
"Type not found in cache: Win8nl.Services.INavigationService."

Joost van Schaik said...

@Shashan
Did you add
SimpleIoc.Default.Register<INavigationService>(() =>{return new NavigationService(rootFrame);});

to your App.xaml as I describe my in my article


Unknown said...

Hello
The navigation works fine but I want to pass a parameter that should be received by the next page....

However I tried to use Messenger class but couldn't succeed.

Hoping for a prompt reply.

Joost van Schaik said...

@Ganesh I usually make sure the selected value goes to the Model or ViewModel, thus making the value available through the whole app...