tags:

views:

607

answers:

4

I have an application that need to open a dialog from a button where the user enters some information.

At the moment I do it like this (which works fine)

  • The button click generates a command in the ViewModel.
  • The ViewModel raises an event which the Controller listens to.
  • The Controller works out the details of the new window (i.e. View, ViewModel & model) and opens it (ShowDialog)
  • When the window is closed the Controller adds the result to the eventargs and returns to the ViewModel
  • The ViewModel passes the information to the Model.

There are a lot of steps but they all make sense and there is not much typing.

The code looks like this (the window asks for the user's name)

ViewModel:

AskUserNameCommand = DelegateCommand(AskUserNameExecute);
...

public event EventHandler<AskUserEventArgs> AskUserName;

void AskUserNameExecute(object arg) {
    var e = new AskUserNameEventArgs();
    AskUserName(this, e);
    mModel.SetUserName(e.UserName);
}

Controller:

mViewModel.AskUserName += (sender,e) => {
    var view = container.Resolve<IAskUserNameView>();
    var model = container.Resolve<IAskUserNameModel>();
    var viewmodel = container.Resolve<IAskUserNameViewModel>(view, model);
    if (dlg.ShowDialog() ?? false)
        e.UserName = model.UserName;
}

My question is how the horizontal communication works in the MVVM pattern. Somehow it seems wrong to let the controller be involved in the data transfer between the models.

I have looked at the mediator pattern to let the models communicate directly. Don't like that idea since it makes the model depending on implemetations details of the GUI. (i.e. if the dialog is replaced with a textbox, the model need to change)

A: 

I have come across similar problems. Here is how I have solved them, and why I have done what I have done.

My solution:

My MainWindowViewModel has a property of type ModalViewModelBase called Modal. If my code needs a certain view to be modal, it puts a reference to it in this property. The MainWindowView watches this property through the INotifyPropertyChanged mechanism. If Modal is set to some VM, the MainWindowView class will take the VM and put it in a ModalView window where the appropriate UserControl will be shown through the magic of DataTemplates, the window is shown using ShowDialog. ModalViewModelBase has a property for DialogResult and a property called IsFinished. When IsFinished is set to true by the modal VM, the view closes.

I also have some special tricks for doing interactive things like this from backgroundworker threads that want to ask the user for input.

My reasoning:

The principle of modal views is that other views are disabled, while the modal is shown. This is a part of the logic of the View that is essentially lookless. That's why I have a property for it in the MainWindowViewModel. It I were to take it further, I should make every other property or command for all other VM's in the Main VM throw exceptions, while in modal mode, but I feel this to be excessive.

The View mechanism of actually denying the user any other actions, does not have to be performed with a popup window and showdialog, it could be that you put the modal view in the existing window, but disable all others, or some other thing. This view-related logic belongs in the view itself. (That a typical designer can't code for this logic, seems a secondary concern. We all need help some times.)

So that's how I have done it. I offer it only as a suggestion, there is probably other ways of thinking about it, and I hope you get more replies too.

Guge
That is an interesting way but I you don't say how the data transfer works. How does the user input get from the Dialog(View)Model to the Main(View)Model when the dialog is closed?
adrianm
The MainViewModel still has a reference to the DialogViewModel after the dialog view is closed.
Guge
ok, that is more or less the same thing as I do but you do it in the viewmodel.
adrianm
A: 

I've used EventAggregator from Prism v2 in similar scenarios. Good thing about prims is that, you don't have to use entire framework in your MVVM application. You can extract EventAggregator functionality and use it along with your current setup.

Raj
The EventAggregator is nice but it is a broadcast service. I can't find an easy way to configure it to call a specific instance of a listener. Assume the user opens the dialog, closes it and opens it again. I now have two instances of view/viewmodel/model which listens to the event until the GC starts cleaning.This can be solved if the listener unsubscribe from the event but that leads to some kind of IDisposable pattern in the ViewModel/Model.
adrianm
@adrianm you are correct. If your wish is for your message to be delivered to a specific instance the Event Aggregator is not appropriate.
Anderson Imes
A: 

You might have a look at this MVVM article. It describes how a controller can communicate with the ViewModel:

http://waf.codeplex.com/wikipage?title=Model-View-ViewModel%20Pattern&amp;ProjectName=waf

xdoo
+4  A: 

I don't like most of the current suggestions for one reason or another, so I thought I would link to a nearly identical question with answers I do like:

http://stackoverflow.com/questions/1043918/open-file-dialog-mvvm/1044304#1044304

Specifically the answer by Cameron MacFarland is exactly what I do. A service provided via an interface to provide IO and/or user interation is the way to go here, for the following reasons:

  • It is testable
  • It abstracts away the implementation of any dialogs so that your strategy for handling these types of things can be changed without affecting constituent code
  • Does not rely on any communication patterns. A lot of suggestions you see out there rely on a mediator, like the Event Aggregator. These solutions rely on implementing two-way communication with partners on the other side of the mediator, which is both hard to implement and a very loose contract.
  • ViewModels remain autonomous. I, like you, don't feel right given communication between the controller and the ViewModel. The ViewModel should remain autonomous if for no other reason that this eases testability.

Hope this helps.

Anderson Imes
Thanks, that got me thinking. I don't want to inject the ioc-container into the viewmodel so I will probably create some kind of IControllerService that the viewmodel can call. Will be much cleaner than the event based communication I got now.
adrianm
If you are using IoC, I would just declare something like your IDialogService as a dependency. I typically think of the IoC as a catalog of services available to all constituent objects.
Anderson Imes