Clean and Elegant View Models in WPF

Once I learned MVVM, I really enjoyed it. The whole concept of binding, separating view from logic, testability, etc. She was very encouraging. It was a good alternative to messy, endless code. Than I found out, there are teams that can be linked, which I also liked at the beginning.

After writing a couple of controls with MVVM, I found that my view models were starting to look more or less code-like. Complete commands that almost exactly did what was previously done in the code behind the event handlers.

Let me give you some examples.

There is a control with a Details button that opens another window.

[approach 1] The first (and worst) thing you can do is call something like this on the command line:

new DetailsWindow().ShowDialog();

      

This makes the view model heavily referenced to the view layer - ugly.

[approach 2] Let's fix this problem using a weak reference and create something like IDialogService. We can introduce a simple implementation that creates and opens a window. Now we got rid of the strong reference to the presentation level, and the team might look like this:

_dialogService.ShowDetailsWindow();

      

I still don't like this approach. It seems to me that the view model is not something that has to decide whether to show the window or not. It must serve and process data.

[approach 3] An elegant way to completely decouple the presentation model from the presentation tier would be to issue a command. Than the view model won't know about the view layer. It will only perform the injection action - no matter what it is.

Question 1:

Which approach would be better? I think the number 3 is the winner.

Question 2:

Does it have to be part of the view model? I think it shouldn't be as it seems, it is about the presentation layer. Maybe your best bet is to put it in a simple event handler in your code?

The second example is more complex. In the same control, we have the Delete button. It should open a dialog box asking the user for confirmation, and if he says yes, he should delete something. In this case, it makes sense to put it in the view model, since it does affect the data.

Question 3

This case is the most difficult for me. I can't use my favorite approach number 3 , because I need to display a dialog that is a view layer task, but also I need to do some logic depending on the dialog result, which on the other hand considers the model to work . What's the best approach here?

Please note that I really don't want to use approaches 1 and 2 . I want the view model to be clean and not aware of anything related to the presentation layer, not even with weak references.

One thing that comes to my mind is to split the view model layer into two layers. Something like:

view -> view view model -> boolean view model

  • view view model
    • used as a control context
    • contains the boolean view model as a public property for direct bindings
    • uses approach number 2 - this is now acceptable since the entire class is dedicated to performing presentation-related activities.
  • boolean model
    • this is "presentation free"
    • links to specialized logical services
    • some commands can be linked directly to the view
    • some commands can be executed by the view view model it owns

Perhaps this is the right way?

[EDIT]

In response to suggestions in the comments about using the framework:

Using a framework will undoubtedly make window handling easier, but this is not a solution to the problem. I don't want the "view logic model" to handle windows at all, even with a framework. Referring to the approach I suggested at the end, I would still put it in the "viewmodel"

+3


source to share


1 answer


Your ViewModel should simply notify all subscribers that the command has been completed by firing a simple event. The View should subscribe to this event and handle the event by displaying a new window ...

ViewModel:

public event EventHandler<NotificationEventArgs<string>> DisplayDetailsNotice;

private DelegateCommand displayDetailsCommand;
public DelegateCommand DisplayDetailsCommand
{
    get { return displayDetailsCommand ?? (displayDetailsCommand = new DelegateCommand(DisplayDetails)); }
    }

public void DisplayDetailsWindow()
{
    //
    Notify(DisplayDetailsNotice, new NotificationEventArgs<string("DisplayDetails"));
}

      

View (note the VM is already a DataContext View):



private readonly MyViewModel mvm;

//Get reference to VM and subscribe to VM event(s) in Constructor

mvm = DataContext as MyViewModel;
if (mvm == null) return;
mvm.DisplayDetailsNotice += DisplayDetails;

private void DisplayDetails(object sender, NotificationEventArgs<string> e)
{
    DetailsWindow = new DetailsWindow { Owner = this };
    DetailsWindow.ShowDialog();
}

      

Thus, when the command is executed, the virtual machine raises the event, and the view that subscribes to the VM events handles the event by displaying details windows using the ShowDialog()

. Since all VMs publish the event, it remains agnostic and decoupled from the view, leaving the MVVM pattern unchanged. Since the VM is already DataContext View and the View binds to the properties of the VM, since it gets a reference to the VM, it also doesn't break the MVVM pattern ...

Please note that this syntax is valid with the MVVM Framework MVVM Toolkit. As suggested in the comments, you should start using the MVVM framework (my recommendation is a simple MVVM Toolkit or MVVM Light, Caliburn is too complex for no reason and has no IMO payoff) to avoid having to deal with plumbing yourself ...

0


source







All Articles