Introduction
Most of the MEFedMVVM features so far were all around discoverability of ViewModels, yet when building MVVM application sometimes you want to also have a mechanism to discover and launch views. If we look at web development its all about resources sitting on a server and you can launch/load a specific resource via a URI (Unique Resource Identifier). This mechanism proved to be a very easy and scalable way of locating views and within a Web Page you can link to other pages very easily. When building WPF applications that are purely content based such a mechanism would really come in handy, and if you think about it MEFedMVVM is all about discoverability so why not support this scenario.
Since this is not really part of the core MEFedMVVM I created an extension that you can use to accomplish this, MEFedMVVM NavigationExtension.
MEFedMVVM.NavigationExtension support both WPF and Silverlight 4.
Enter MEFedMVVM Navigation Extensions
The idea is to be able to specify to a View that it can be located by a unique identifier (a string) and then you can have someway of launching that view and render it in some container/host. Something like this
And you make the view discoverable by decorating it with this attribute
As you can see in the figure above, there are 3 magic attached properties that are attached to the “Invoker”
- NavigationExtensions.NavigateTo
- Specify the unique identifier (string) to locate the view. I use a URI format but you can use whatever you like as long as its unique
- NavigationExtensions.NavigationHost
- Specify where you want the View to be rendered
- NavigationExtensions.NavigationParameter
- Specify a Parameter to be passed to the ViewModel of the View. The reason why the parameter is passed to its ViewModel is because if you are doing MVVM then your View has no need for parameters, its the ViewModel that needs the parameter(after all the ViewModel controls the logic). We will see how you can still cheat and do whatever you like at the end of the day, the parameter can be passed to the View.
So one might wonder how will my ViewModel receive the parameter. This is done by your ViewModel being set as DataContext of the View (if you are using MEFedMVVM to link the View to the ViewModel this happens automatically) and also your ViewModel has to implement the INavigationInfoSubscriber interface. This interface defines 1 method OnNavigationChanged which will pass along the parameter and also give you an instance of the INavigationManager responsible for starting the Navigation.
Recap
So till now we can
- Make a View discoverable by specifying a Unique Identifier
- Specify an Invoker and give it enough information on what to render and where to render it
- And also specify a parameter to be passed
This pretty much covers the bare basics, let’s get a better understanding of what is a Host and what is an Invoker before we deep dive in more complex scenarios.
Host and Invoker Deep Dive
When building the NavigationExtensions I wanted to make sure that you can create your own handlers both for Hosts and Invokers, and what is the best way to do so if not with MEF
There are 2 base classes you need to write in order to create your own handlers.
- ControlNavigationHost
- This is to create your own hosting control. Out of the box you get one which is ContentControlNavigationHost (it handles any ContentControl)
- ControlNavigationHandler
- This is to create your own invoker for a control. Out of the box you get one which is the ButtonNavigationHandler (it handles any ButtonBase)
The ControlNavigationHost has 4 methods that you need to implement (all method implementation would be usually one liners)
In order to make your own ControlNavigationHost discoverable by the NavigationExtensions simple Export it like this
The ControlNavigationHandler has 3 methods you need to implement
In the implementation you simple have to register to the Event you want and then call the OnEventFired protected method of the base class. here is an example
And again to make the handler discoverable you Export it like so
Please note: that its up to you how you want the creation policy to be (i.e. If MEF should create a new instance of the NavigationHandler or not but in this case you should always make it NonShared so that for each invoker in your application you have a different ControlNavigationHandler instance)
Apps are usually more complicated, so let’s dive into more complicated scenarios
Before we start going through these scenarios let’s have a look at some interfaces and classes that MEFedMVVM exposes for you to consume/implement
INavigationManager
INavigationManagerProvider
Implement this interface on a class that will be passed as NavigationParameter and you will get injected with a INavigationManager responsible for that Navigation
INavigationInfoSubscriber
Implement this interface in your ViewModel to get passed the NavigationParameter.
NavigationCommand<T>
A NavigationCommand is just a DelegateCommand<T> BUT it implements the INavigationManagerProvider interface. When used as a NavigationParameter it will hold the instance of the INavigationManager so that you can do things such as Closing a navigation. We will see the NavigationCommand<T> being used in the first scenario below.
Scenario 1
Let’s say you have a dialog that shows some settings and when you are done you want to get those settings back to the original ViewModel that “started” the navigation to the Settings screen. Here are a couple of screen shots for such a scenario.
In order to do this we need the MainViewModel to expose a NavigationCommand<T>
and the Execute handler for this would be something like this
We will revisit the code inside the Execute Handler in a bit**…
Now we can specify that the NavigationParameter is this command so that the SettingsViewModel can execute this command when it is done and give us the ApplicationSettings object instance.
The Settings ViewModel implements the INavigationInfoSubscriber thus it will get injected with the NavigationCommand that we are passing to it via the NavigationParameter attached property
Once the Settings ViewModel calls the Execute on the _onSettingChangedCommand it will invoke the method inside the MainViewModel (OnSettingChangedExecuted) passing the new ApplicationSettings.
**One thing to note is that the MainViewModel is also calling CloseNavigation on the NavigationManager of the NavigationCommand. This is so that as soon as its done applying the new settings the Settings screen disappears.
Download the sample and play around with it to get a better feel of how this all works together (its under Samples/TestNavigation)
Scenario 2
Let’s say you have a sort of Wizard Step by Step UI.
In this case we want to chain the Navigation so that the CreateUserProfileViewModel send the UserProfile not to the MainViewModel (the ViewModel that started the Navigation) but to the ViewModel next in the chain i.e. the RenderUserProfileViewModel.
In order to do so both “Invokers” (i.e. the button for the CreateUserProfile and the button for the RenderUserProfile) must have the same navigation “invoker”. You do so by explicitly setting the NavigationHander attached property (this is an attached property that exposes the Navigation handler for an “invoker”).
Ok so now we have both “invokers” using the same NavigationHandler; because of this we can register to the NavigatingAway event of the INavigationManager inside the CreateProfileViewModel and pass the data we want to the RenderUserProfileViewModel (which is the NewNavigationInfoSubsciber in the NavigationEventArgs passed by the event)
So basically the CreateUserProfileViewModel (Step 1) could pass along data to RenderUserProfileViewModel (Step 2) and you can continue chaining like this one step after another.
NOTE: For Silverlight you instead of using the NavigationExtensions.NavigationHandler use the NavigationExtensions.ChainToElement and specify the other button (this is because there are issues around binding to custom attached properties in SL). This approach can also be used in WPF.
Download the sample and play around with it to get a better feel of how this all works together (its under Samples/TestNavigation)
Conclusion
One thing I love about this Extension is that it enables you to use View-First approach to MVVM in nearly any scenario. Yes granted sometimes its better to have ViewModel-First approach but in my experience if you can always work using View-First life becomes much more easy because your code is more loosely coupled. In fact this is one of the things I love about MVC and Web in general… Controllers never reference each other, A View has a controller and thats it. In MVVM we tend to complicate things by having Parent ViewModels that have Child ViewModels yada yada yada… just my 2 cents…
This is all still work in progress, it needs more testing from my end to make sure there are no side effects such as memory leaks etc yet feel free to poke around and play around with it. As always feedback/bug reports are very welcome.
Download the code from http://mefedmvvm.codeplex.com/SourceControl/list/changesets