tags:

views:

107

answers:

4

The scenario:

  1. Parent Window loads, fills data (grid, labels, etc.)
  2. User clicks a button, firing a bound command in the Parent Window's ViewModel, launching an Edit Dialog
  3. User makes changes in the Edit Dialog and clicks "Accept", saving some information to a database
  4. Parent Window's ViewModel recognizes that something happened (noticing that dialog.DialogResult == true), and that it should refresh it's data and any relevant bindings

Also consider that step 3 could fail for some reason (a complex business logic rule could fail and the user needs to tweak something, the database update could throw an exception, zombie invasion, etc.), so the Edit Dialog might have to stay open.

ViewModels shouldn't (to my understanding) know much/anything about its associated View, and therefore, it would be difficult for the ViewModel to communicate to the View:

"Hey, the Save operation completed successfully! Go ahead and close now!"

Or:

"Whoa! There was a problem that needs to be taken care of before you can close... wait a tick."

The Question:

How is something like this elegantly accomplished, with as little xaml.cs code as possible (preferably none)?

+1  A: 

Your EditDialog could have an IsVisible property which the dialog visibility is bound to. If the save fails, leave the IsVisible property set to true, otherwise set it false. Since it's bound, it should automatically hide the Popup when the save finishes successfully, or leave it open if it fails.

Rachel
+2  A: 

Normally when doing something like this I hand the opening of dialog windows off to a service that can later be mocked for testing purposes. Something like:

public interface IDialogProvider
{
      bool OpenDialog(IViewModel viewModel)
}

Then the passed in viewmodel can bound be set as the DataContext of modal window and, through data templating the correct view will be used.

The parent view model can use this service when the command is triggered to open up the child. The child can do all processing required and any information required can be drawn from the child on a true result:

public void ExecuteEdit()
{
    ChildViewModel childViewModel = GetViewModelForSelectedItem();
    if (_dialogProvider.OpenDialog(childViewModel)
    {
        //child view model saved, trigger rebinding etc...
    }
}

To handle the error scenario if you have a specific base class for the viewmodels used by these dialogs you can have a little code behind that handles the closing of the window. I'd use a view agnostic event in the view model and close the window when this event is triggered. In the view:

public DialogWindowView()
{
    InitializeComponent();
    DataContexctChanged += HandleDataContextChanged;
}

private void HandleDataContextChanged(object sender, EventArgs e)
{ 
    IDialogViewModel viewModel = DataContext as IDialogViewModel;

    if (viewModel != null)
    {
         viewModel.ActionSuccessful += HandleActionSuccessful
    }
}

private void HandleActionSuccessful(object sender, EventArgs e)
{
     DialogResult = DialogResult.OK;
     Close();
} 

I have no problem at all with this code being the the view since it is not the responsibility of the viewmodel to handle what things look like, and that includes opening or closing windows. If you are completely against the idea of code behind, or you don't want to couple the view to a viewmodel type, you could move the responsibility to the DialogProvider instead:

public class DialogProvider : IDialogProvider
{
  public bool OpenDialog(IDialogViewModel viewModel)
  {
       Window dialog = new DialogWindow();
       dialog.DataContext = viewModel;
       bool success = false;
       viewModel.ActionSuccessful = (o, e) => 
         {
              dialog.Close();
              success = true;
         }

      dialog.ShowDialog();

      return success;
  }
}
Martin Harris
I'd put that in the dialog provider. It's a classic behavior that probably all your modal dialogs will use...
Stephane
A: 

Nice question.
The MVVM pattern states that the View should databind to the ViewModel.Properties. The View can trigger actions by calling on to ViewModel.Commands (View => VM).
Hence the only way for the ViewModel to communicate something back to the View is via Properties (i.e. PropertyChangedNotifications). The View would have to bind to some property on the VM and on a change, decide whether to close itself or not.

Gishu
A: 

I rolled my own window loader which is described in my answer here:

http://stackoverflow.com/questions/1828043/managing-multiple-wpf-views-in-an-application/1828258#1828258

MarkB