Navigation in WPF is easy – unless of course you’re trying to apply an MVVM pattern. Most examples tell you about all the great things you can do with MVVM/WPF whilst brushing such things as navigation under the carpet. I’ve not found one person who’s adequately explained an MVVM example with ‘all’ of the facets you’ll need when writing a real application.
I’m not going to tell you how to implement full-scale configurable multi-context navigation using MVVM, but I’ll briefly discuss one approach to a nagging issue – that of triggering and controlling navigation from the ViewModel. I’m also talking specifically about ‘pages’ here too, as I’m targeting a browser with this application.
WPF ‘Page’ objects expose a NavigationService property, which hooks into the WPF navigation framework. This is very convenient and powerful. MVVM effectively steers you away from doing anything behind your ‘views’, and tries to substitute the traditional coupling between view/controller/viewmodel (depending on the flavour) with reliance on data binding to give the viewmodel everything it needs to perform all the UI logic.
Your ViewModel isn’t supposed to have any reference to (or knowledge of) your view. This means you won’t have a reference to the Page to be able to access its NavigationService. There’s other ways to navigate, like using more of a frame appropach (most of the examples so this), but if you want to navigate ‘web-style’ from Page1 to Page2 to Page3 etc – controlling this from your ViewModel, what do you do?
After messing around with quite a number of approaches I’ve currently settled for a very simple technique that doesn’t feel ‘too’ dirty. It became clear (in my case) that loading the ViewModel from the View is actually more appropriate and practical than loading the view from the ViewModel (through a DataTemplate mapping as others would suggest). I found starting everything from the ViewModel paints you into something of a technical corner, as some core WPF functionality only exists at the view level. You can of course write your own implementations, but I’ve always thought patterns are meant to ‘help’, and when they cease to help, you stop.
In our example, the Application object effectively sets things up by being the all-seeing eye on the navigation framework.
The code below simply sets the startup uri (from another library), and subscribes to the ‘navigated’ event, which will fire after every page movement.
public partial class App : Application
{
private static NavigationService navigator;
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
this.StartupUri =
new Uri("pack://application:,,,/MyPageLibrary;component/MyStartupPage.xaml");
}
void App_Navigated(object sender, NavigationEventArgs e)
{
Page page = e.Content as Page;
if (page != null)
ApplicationHelper.NavigationService = page.NavigationService;
}
}
The ApplicationHelper class is a simple static implementation to provide the whole application with what is in effect a ‘bus’ service – the means to navigate, using the NavigationService injected from the Application. I said this was simple.
public static class ApplicationHelper
{
private static NavigationService navigator;
public static NavigationService NavigationService
{
set
{
navigator = value;
}
get
{
return navigator;
}
}
}
The ViewModel is then free to navigate whereever it likes (I’m constructing the pages as objects here with parameters to use in constructing the viewmodel, rather than using a uri).
//Now navigate to the detail view
//Datacontext used to construct the ViewModel
MyNextPage nextPage = new MyNextPage(SomeDataContext);
ApplicationHelper.NavigationService.Navigate(nextPage);
I’m sure this will evolve again (like everything I’m finding with WPF), but for now this seems to perform my basic requirements
Thanks for posting this, Matt. I’m finding building a navigation-style MVVM application a frustrating experience as well. I’m all for applying simple hacks like this one if it means I can be more productive with WPF.
One suggestion: rather than hooking the Navigated event, why don’t you override the OnNavigated method, like you do with OnStartup? It ought to be slightly more efficient.
Dean – Thanks for the comment. I think that was probably an oversight on my part! I’ll change that when I get chance. Cheers, Matt