Nested ViewModels

Over the past couple of days I've been working on a ground-up rewrite of Halfwit as a proof of concept. The idea was to create a new WPF application and use Nuget to grab MVVM Light and Autofac and build from there.

In Halfwit, the user interface consists of a "nest" of views. Here's a rough visual representation:

Visual representation of Halfwit's structure

Each rectangle is its own "View" (represented in the app as a Window or UserControl) and each has its own "ViewModel".

Let's focus for this example on the ViewModel that represents each individual tweet. I call it StatusViewModel, and (as you can see) they are presented in a list on the screen, hosted within a timeline. Each tweet has a series of actions it can perform, represented by ICommands on the ViewModel. For example, you can copy a tweet the clipboard, reply to it, retweet it etc. Some of those actions need to hand off control to the "parent" ViewModel (for reasons which will become apparent).

To faciliate this, the StatusViewModel takes a dependency on the TimelineViewModel in the form of a constructor parameter called "host". It doesn't make sense for a tweet to exist without a timeline to live in, so the host ViewModel is a dependency in the true sense of the word. Here's StatusViewModel's constructor:

public StatusViewModel(IMessenger messenger,
    TwitterService service, 
    TimelineViewModel host, 
    ITwitterModel item)
    : base(messenger, host.Service)
{
    _host = host;
    /* ... */
}

Here are a couple of ways we communicate back to the "timeline" from the tweet itself.

Command Tunnelling

When you click a hashtag in a tweet, the StatusViewModel needs to tell its "host" (the TimelineViewModel) to navigate to the search panel and search for other tweets containing that hashtag. (Interestingly, the timeline just passes this request up one more level to the main window using the same technique.)

Now when the user clicks on a hashtag Hyperlink, the "SearchCommand" is invoked. SearchCommand is an ICommand living on StatusViewModel, but it just looks like this:

public ICommand SearchCommand { get { return _host.SearchCommand; } }

So that's a really simple way that a "child" ViewModel can "communicate" with its host - by simply making the host's commands available on the child class.

Method Calling

Some tweets are replies or retweets, and for those Halfwit offers a link to let the user find (downloading, if necessary) the original tweet. We call the command "FindContextCommand" but it can do very little in the StatusViewModel. The timeline itself has to do the bulk of the work.

So we define the FindContextCommand on StatusViewModel like this:

public ICommand FindContextCommand { get; private set; }

... and initialize it in the constructor:

FindContextCommand = new RelayCommand(FindContext, CanFindContext);

(That's MVVM Light's RelayCommand in case you don't recognize it.)

Lastly, the methods that make up FindContextCommand's implementation look like this:

bool CanFindContext()
{
    return RetweetedStatus != null || InReplyToStatusId.HasValue;
}

void FindContext()
{
    _host.FindContext(this);
}

So as you can see, FindContext() by itself simply hands off the call to the "host" timeline. The host then loads the original tweet, adds it to its list, and selects/focuses it.

Messaging

There's one more method that Halfwit to communicate between ViewModels, and it's built into MVVM Light. It's called the Messenger class, and it's kind of a little message bus just for your application.

When you click "Retweet" on a tweet, you need to create a new "RetweetViewModel" and tell the host timeline to show the update panel with this information. It'd be simple enough for us to do that with a method call, but this logic of preparing an update and asking the timeline to display it for editing happens from all over the application, so I decided to make use of the message bus for it.

Here's the code for the RetweetCommand's implementation:

void Retweet()
{
    UpdateViewModel vm = new RetweetViewModel(MessengerInstance, Service, (TwitterStatus)this.Status);
    MessengerInstance.Send(vm);
}

So we're constructing an "UpdateViewModel" (which is what the "update panel" in our diagram above uses) and sending it out to the rest of the application as a message. That message is handled in the TimelineViewModel's constructor like this:

messenger.Register<UpdateViewModel>(this, vm => Update = vm);

So all it's doing is setting an "Update" property to the object that got sent on the message bus. That, in turn, shows the update panel and lets the user start editing the tweet.

Wrapping Up

This article demonstrates a couple of ways to communicate between nested parts of your application. Some of them might rub the MVVM "purists" the wrong way, but they work, and work well. If you haven't already looked at MVVM Light, I can recommend it - it makes creating ViewModels a breeze!

mvvm c# ioc
Posted by: Matt Hamilton
Last revised: 03 Nov, 2024 01:10 PM History

Comments

Renaud
Renaud
30 Nov, 2010 10:37 AM

Hi Matt,

I found your article very interesting as communication between viewmodels is mandatory and too often I saw bad practice at developers who don't know how to handle that.

Regarding your technics, I would only react to the first one (I don't use very often the method calling and I never use messaging as I don't like the lack of vision it implies).

I am a big fan of ViewModel tunneling and I strongly recommend other developers to use it !

The principle is simple (as you describe) : each viewmodel you create receive its parent's viewmodel in its constructor. In such way, you can reach any viewmodel above the current one whatever the number of viewmodel layers there are in your application.

In addition to that, in each view, I bind each nested view to a usercontrol property in the viewmodels so if, after I went up to a parent's viewmodel, I have to go down to another one (different branch), I use the views hierarchy to reach the good view and then the good viewmodel.

Doing so, I still respect one of the basic rule of MVVM which says that your view knows its viewmodel but not the opposite. By the way, as a good practive, you can define a BaseViewModel class from which every other viewmodel inherits and which contains the parent's viewmodel.

30 Nov, 2010 07:54 PM

Thanks for your comment, Renaud! MVVM Light actually gives you a ViewModelBase class to derive your ViewModels from, with a whole bunch of goodies baked in (like INotifyPropertyChanged support, and access to a shared IMessenger instance)!

Vitalij
Vitalij
11 Jun, 2011 07:56 PM

Great post. I myself was using a nested MVVM for quite a while, but surprisingly haven't seen many blog posts on the web. This one in fact is the first one. I think this topics is overlooked by other developers.

Vijay
Vijay
24 Jul, 2012 01:31 AM

This all works fine when you have references of the View models across the modules. I am not sure if this kind of tunnelling works fine in Prism application where the modules are loaded at run time. Any ideas how this can be done in the Prism application.

24 Jul, 2012 02:40 AM

I'm happy to say I've never used Prism, Vijay.

If the modules aren't aware of each other you're probably better off using a message bus approach (like my last example using MVVM Light's IMessenger) to communicate between ViewModels in a loosely-coupled way.

Prism
Prism
13 Aug, 2012 05:04 PM

Use the EventAggregator in prism

No new comments are allowed on this post.