nlawalker
nlawalker

Reputation: 6514

Initializing a viewmodel

Something that continues to confuse me about MVVM - if I use the view-first approach to constructing my objects (this appears to be the most common approach, at least after much reading and searching), how do I get contextual information into the viewmodel?

I've seen many answers to similar questions say "use the DI container to inject your model," but that doesn't help me here, so I'm going to provide a small example.

Let's say my application is a PeopleEditor. It's made to load and edit People objects, which are complex. When you load the application, you get a home screen that loads a bunch of People into memory - let's say that those are all made accessible via a collection that I can get to from my container. By clicking on a Person, you are taken to the editor screen. The editor is complex, so this is not a trivial master-detail view implemented in one screen.

So, on the home screen, when I click a person, the application needs to create a new view and viewmodel and show the view. If I create the viewmodel first, via the container or not, I can initialize it with the appropriate person object. This seems very natural to me, so I'm having a hard time figuring out why view-first seems to be the predominant pattern. How would I do this using the view-first approach? The view would create the viewmodel, which can get to the collection of People but doesn't know which person its editing. EDIT for clarity: Multiple People editors could exist at one time, each editing a different person.

The Prism 4.0 alpha's MVVM reference implementation uses a "state handler", which is basically a service that the app uses to store a constructor parameter in the container. It saves the state and calls ShowView, and the viewmodel that ultimately gets created imports a state object. This seems clunky to me - it's like it's trying to pretend it's loosely coupled when it's really not. Does anyone else have any other guidance?

Upvotes: 10

Views: 6633

Answers (2)

escapechar
escapechar

Reputation: 23

If you are using Prism, you can easily and neatly solve this problem by using its navigation feature. Use IRegionManager.RequestNavigate to navigate from main view to the editing view by constructing the target view's Uri to include a query string param for the corresponding Person's identification. You can extract that id in the target view model's OnNavigatedTo() method implementation (INavigationAware member. The view model should implement this interface).

You can see this in action in the "View-Swithing Navigation" sample app which is shipped with Prism download. Its under Quickstarts folder.

From the same sample app (which imitates Outlook), this following code is used to navigate from InboxView to EmailView in order to open a particular email from inbox:

var builder = new StringBuilder();
builder.Append(EmailViewKey);
var query = new UriQuery();
query.Add(EmailIdKey, document.Id.ToString("N"));
builder.Append(query);
this.regionManager.RequestNavigate(RegionNames.MainContentRegion, new Uri(builder.ToString(), UriKind.Relative));

And in the EmailView's view model EmailViewModel the email to be opened is extracted from the navigation context like this:

 void INavigationAware.OnNavigatedTo(NavigationContext navigationContext)
    {
        // todo: 15 - Orient to the right context
        //
        // When this view model is navigated to, it gathers the
        // requested EmailId from the navigation context's parameters.
        //
        // It also captures the navigation Journal so it
        // can offer a 'go back' command.
        var emailId = GetRequestedEmailId(navigationContext);
        if (emailId.HasValue)
        {
            this.Email = this.emailService.GetEmailDocument(emailId.Value);
        }

        this.navigationJournal = navigationContext.NavigationService.Journal;
    }

 private Guid? GetRequestedEmailId(NavigationContext navigationContext)
    {
        var email = navigationContext.Parameters[EmailIdKey];
        Guid emailId;
        if (email != null && Guid.TryParse(email, out emailId))
        {
            return emailId;
        }

        return null;
    }

Upvotes: 0

ktutnik
ktutnik

Reputation: 7260

nlawalker,

I'm not expert but what i learn about View-First and Model-First is:

  1. View-First: View programs ViewModel, You create view then viewmodel automatically created.
  2. Model-First: ViewModel programs View, You create ViewModel object graph in the root application, assign it to the root view data context. then lets the view render its related child depends on view model.

Don't mean to say Model-First approach is bad but, I prefer View-First approach because viewmodel can sits in code behind, so when some process require non-binding friendly task (PasswordBox, DialogConfirmation, ClosingForm etc), i can write my logic in code behind.

Anyway, To solve the case I usually used combination of IOC and Event Aggregator. Here it is:

  1. For viewmodel requires contextual information register its instance in IOC container either than its type. So it is alyas ready even its view not.
  2. When navigation action occur (by clicking on people list item) resolve your view with IOC container resolver. and send an event to navigation bus with specified parameter. Further this event will catch by target ViewModel and do something.

Registering instance of viewmodel is not really necessary. it is only to make sure that viewmodel is ready when event dispatched by the previous viewmodel.

UPDATE

but then to populate it with any kind of local context I need to use a global facility to send it an event?

In your case, contextual object isn't local it is rather a message passed between object invocation. Obviously in your model-first approach you do:

//selectedPeople is contextual object
myPeopleDetailVM.LoadData(selectedPeople)

it will pretty much the same when you pass selectedPeople to the argument of the event bus.

If you considering performance, than you can compare it with the WPF Routed Event System in this case Routing strategy is more complex than Event Bus and I think if you confident enough using WPF routed event than you should with Event Aggregator.

The only problem i see if you use built-in framework event aggregator (prism, mvvmlight), your viewmodel is polluted with event bus, if you complaining about this then i agree with you.

Hope that help.

Upvotes: 3

Related Questions