40

Example #1: I have a view displayed in my MVVM application (let's use Silverlight for the purposes of the discussion) and I click on a button that should take me to a new page.

Example #2: That same view has another button that, when clicked, should open up a details view in a child window (dialog).

We know that there will be Command objects exposed by our ViewModel bound to the buttons with methods that respond to the user's click. But, what then? How do we complete the action? Even if we use a so-called NavigationService, what are we telling it?

To be more specific, in a traditional View-first model (like URL-based navigation schemes such as on the web or the SL built-in navigation framework) the Command objects would have to know what View to display next. That seems to cross the line when it comes to the separation of concerns promoted by the pattern.

On the other hand, if the button wasn't wired to a Command object and behaved like a hyperlink, the navigation rules could be defined in the markup. But do we want the Views to control application flow and isn't navigation just another type of business logic? (I can say yes in some cases and no in others.)

To me, the utopian implementation of the MVVM pattern (and I've heard others profess this) would be to have the ViewModel wired in such a way that the application can run headless (i.e. no Views). This provides the most surface area for code-based testing and makes the Views a true skin on the application. And my ViewModel shouldn't care if it displayed in the main window, a floating panel or a child window, should it?

According to this apprach, it is up to some other mechanism at runtime to 'bind' what View should be displayed for each ViewModel. But what if we want to share a View with multiple ViewModels or vice versa?

So given the need to manage the View-ViewModel relationship so we know what to display when along with the need to navigate between views, including displaying child windows / dialogs, how do we truly accomplish this in the MVVM pattern?

SonOfPirate
  • 2,935

3 Answers3

27

Navigation should always be handled in the ViewModel.

You're on the right track with thinking that the perfect implementation of the MVVM design pattern would mean you could run your application entirely without Views, and you can't do that if your Views control your Navigation.

I usually have an ApplicationViewModel, or ShellViewModel, which handles the overall state of my application. This includes the CurrentPage (which is a ViewModel) and the code for handling ChangePageEvents. (It's often also used for other application-wide objects such as the CurrentUser, or ErrorMessages too)

So if any ViewModel, anywhere, broadcasts a ChangePageEvent(new SomePageViewModel), the ShellViewModel will pickup that message and switch the CurrentPage to whatever page was specified in the message.

I actually wrote a blog post about Navigation with MVVM if you're interested

Rachel
  • 24,037
7

For the sake of closure, I thought I would post the direction I finally chose to solve this problem.

The first decision was to leverage the Silverlight Page Navigation framework provided out-of-the-box. This decision was based on a several factors including knowledge that this type of navigation is being carried forward by Microsoft into Windows 8 Metro apps and is consistent with navigation in Phone 7 apps.

To make it work, I next looked at the work the ASP.NET MVC has done with convention-based navigation. The Frame control uses URIs to locate the 'page' to display. The similarity provided an opportunity to use a similar convention-based approach in the Silverlight app. The trick was making it all work together in an MVVM way.

The solution is the NavigationService. This service exposes several methods, such as NavigateTo and Back, that ViewModels can use to initiate a page change. When a new page is requested, the NavigationService sends a CurrentPageChangedMessage using the MVVMLight Messenger feature.

The view that contains the Frame control has its own ViewModel set as the DataContext that is listening for this message. When received, the name of the new view is put through a mapping function that applies our convention rules and set to the CurrentPage property. The Source property of the Frame control is bound to the CurrentPage property. As a result, setting the property updates the Source and triggers navigation.

Going back to the NavigationService. The NavigateTo method accepts the name of the target page. To make sure that the ViewModels have no UI concerns, the name used is the name of the ViewModel to display. I actually created an enumeration that has a field for each navigable ViewModel as a helper and to eliminate magic strings all over the app. The mapping function I mentioned above will strip the "ViewModel" suffix from the name, append "Page" to the name and set the full name to "Views{Name}Page.xaml".

So, for example, to navigate to the customer details view, I can call:

NavigationService.NavigateTo(ViewModels.CustomerDetails);

The value of CustomerDetails is "CustomerDetailsViewModel" which is mapped to "Views\CustomerDetailsPage.xaml".

The beauty of this approach is that the UI is completely decoupled from the ViewModels yet we have full navigation support. I can now reskin my application however and whenever I see fit without any code changes.

Hope the explanation helps.

SonOfPirate
  • 2,935
3

Similar to what Rachel said, my mostly-MVVM application has a Presenter to handle switches between windows or pages. Rachel calls this an ApplicationViewModel, but in my experience, it generally has to do more than just be a binding target (such as receiving messages, create Windows, etc.) so it's technically more like a traditional Presenter or Controller.

In my application, my Presenter starts with a CurrentViewModel. The Presenter intercepts all communication between the View and the ViewModel. One of the things the ViewModel can do during an interaction is return a new ViewModel, which means a new page or a new Window should be displayed. The Presenter takes care of creating or overwriting the View for the new ViewModel and setting the DataContext.

The result of an action can also be that a ViewModel is "complete", in which case the Presenter detects this and closes the window, or pops this ViewModel off the VM stack and goes back to displaying the previous page.