tags:

views:

1175

answers:

4
A: 

New and delete from a list would be good examples. In those cases, a blank record is added or the current record is deleted by the ViewModel. Any action taken by the view should be in response to those events occurring.

Matthew Wright
So if I click the new button, what happens? The view should create a new instance of the ViewModel, and pass it in to the CurrentVM.Add(newlyCreatedVM) though right?
Micah
Let's say I delete a customer record via the DeleteCommand living on the VM. the VM calls into the business layer and tries to delete the record. It can't so it returns a message to the VM. I want to display this message in dialogbox. How does the view get the message out of the command action?
Micah
A: 

One way would be to use a command parameter object that the business layer can modify and your VM can process after the command in executed.

siz
+1  A: 

For your delete message box case, I abstract out messageboxes via an interface. Similar to this. I also inject these interfaces for my WPF app.

Constructor

    public MyViewModel(IMessage msg)
    {
      _msg = msg;
    }

Then, in the method delete method on the ViewModel...something like

    public void Delete()
    {
      if(CanDelete)
      {
        //do the delete 
      }
      else
      {
        _msg.Show("You can't delete this record");
      }
    }

This will make it testable, you can plug in a different IMessage implementations that don't actually show a messagebox. Those might just print to the console, for testing purposes. Obviously your WPF app might have an implementation like

public class MessageBoxQuestion : IMessage
{
   public void Show(string message)
   {
     MessageBox.Show(message);
   }
}

Doing this makes testing the different routes(think Yes/No dialogs) very easy and straight forward. You can imagine a delete confirmation. You could either use a concrete instance of the IMessage to return true/false for confirmation or mock out the container during your test.

[Test]
public void Can_Cancel_Delete()
{
  var vm = new ProductViewModel(_cancel);
  ...

}
[Test]
public void Can_Confirm_Delete()
{
  var vm = new ProductViewModel(_yes);
  ...

}

For your other question on when to use the Command, I instantiate the Add New or Details views from the View in question. Just as you have in your example. Views are only instantiated by other Views in our app. I don't use a command in those cases. I do however use the ViewModel properties of the parent view to the child view.

public void Object_DoubleClick(object sender, EventArgs e)
{
  var detailView = new DetailView(ViewModel.Product);
  detailView.Show();
}

Hope this helps!

Jab
In my understanding of MVV, "upcalling" from the VM into the V - even via an interface - is no good. I'd just set a property (like ErrorText or IDataErrorInfo) and let the V deal with it via PropertyChanged
David Schmitt
David, could you elaborate why "upcalling" is not a good idea? This would be very helpful!
Sam
+2  A: 

Never thought I'd see myself being quoted in a question.

I pondered this question myself for some time, and made a rather pragmatic decision for my code base:

In my code base, the ViewModel is getting called when actions happen, and I wanted it to stay this way. Additionally I do not want the ViewModel to control the views.

What did I do?
I added a controller for navigation:

public interface INavigation
{
  void NewContent(ViewModel viewmodel);
  void NewWindow(ViewModel viewmodel);
}

This controller does contain two actions: NewContent() does show new content in the current window, NewWindow() creates a new Window, populates it with the content and shows it.
Of course my viewmodels have no clue which view to show. But they do know which viewmodel they want to show, so according to your example when DeleteCommand is executed, it would call the navigation service function NewWindow(new ValidateCustomerDeletedViewModel()) to show a window stating 'the customer has been deleted' (overkill for this simple messagebox, but it would be easy to have a special navigator function for simple messageboxes).

How does the viewmodel get the navigation service?

My viewmodel class has a property for the navigation controller:

public class ViewModel
{
  public INavigation Navigator { get; set; }
  [...]
}

When a viewmodel is attached to a window (or whatever displays the view), the window will set the Navigator property, so the viewmodel can call it.

How does the navigator create the view to the viewmodel?

You could have a simple list which view to create for which viewmodel, in my case I can use simple reflection since the names are matching:

public static FrameworkElement CreateView(ViewModel viewmodel)
{
  Type vmt = viewmodel.GetType();
  // big bad dirty hack to get the name of the view, but it works *cough*
  Type vt = Type.GetType(vmt.AssemblyQualifiedName.Replace("ViewModel, ", "View, ")); 
  return (FrameworkElement)Activator.CreateInstance(vt, viewmodel);
}

Of course the view needs a constructor accepting the viewmodel as parameter:

public partial class ValidateCustomerDeletedView : UserControl
{
  public ValidateCustomerDeletedView(ValidateCustomerDeletedViewModel dac)
  {
    InitializeComponent();
    this.DataContext = dac;
  }
}

How does my window look like?

Simple: my main window does implement the INavigation interface, and shows a start page on creation. See for yourself:

public partial class MainWindow : Window, INavigation
{
  public MainWindow()
  {
    InitializeComponent();
    NewContent(new StartPageViewModel());
  }

  public MainWindow(ViewModel newcontrol)
  {
    InitializeComponent();
    NewContent(newcontrol);
  }

  #region INavigation Member
  public void NewContent(ViewModel newviewmodel)
  {
    newviewmodel.Navigator = this;
    FrameworkElement ui = App.CreateView(newviewmodel);
    this.Content = ui;
    this.DataContext = ui.DataContext;
  }

  public void NewWindow(ViewModel viewModel)
  {
    MainWindow newwindow = new MainWindow(viewModel);
    newwindow.Show();
  }
  #endregion
}

(This does work equally well with a NavigationWindow and wrapping the view into a Page)

Of course this is testable, since the navigation controller can be mocked easily.

I'm not really sure if this is a perfect solution, but it works nicely for me right now. Any ideas and comments are welcome!

Sam