WPF MVVM dialog example

37,958

Solution 1

I would use a service to display the dialog. The service can then also link views with viewmodels.

public interface IDialogService {
    void RegisterView<TView, TViewModel>() where TViewModel:IDialogViewModel;
    bool? ShowDialog(IDialogViewModel viewModel);
}

public interface IDialogViewModel {
    bool CanClose();
    void Close();
}


RegisterView just links the view type with the ViewModel type. You can set up these links in the module initialization. This is simpler than trying to get modules to register datatemplates in the top layer of your application.

ShowDialog Shows the ViewModel you want to display. It returns true, false and null for close just like the Window.ShowDialog method. The implementation just creates a new view of type TView from your container, hooks it up to the provided ViewModel, and shows it.

IDialogViewModel provides a mechanism for the ViewModel to do verification and cancel the closing of the dialog.

I have a standard dialog window, with a content control in it. When ShowDialog is called it creates a new standard dialog, adds the view to the content control, hooks up the ViewModel and displays it. The standard dialog already has [OK] and [Cancel] buttons with the appropriate logic to call the right methods from IDialogViewModel.

Solution 2

The way I do this is using the mediator pattern also. When the ViewModel wants to show a dialog, it sends a message which is picked up by the application's main window. The message contains an instance of the ViewModel used by the dialog.

The main window then constructs an instance of the dialog window, passes the view model to it and shows the dialog. The result of the dialog is passed back to the caller in the original message.

It looks something like this:

In your view model:

DialogViewModel viewModel = new DialogViewModel(...);
ShowDialogMessage message = new ShowDialogMessage(viewModel);

_messenger.Broadcast(message);

if (message.Result == true)
{
    ...
}

In the main window codebehind:

void RecieveShowDialogMessage(ShowDialogMessage message)
{
    DialogWindow w = new DialogWindow();
    w.DataContext = message.ViewModel;
    message.Result = w.ShowDialog();
}

I hope this is enough to give you the idea...

Solution 3

I'm agreed, that using service to display dialog according to MVVM pattern is the most simple solution. But, I also asked myself, if there are 3 assemblies in my project Model, ViewModel, View and according to MVVM pattern assembly ViewModel has a reference to Model, and View to both Model and ViewModel where should I place DialogService class? If I will place one in the ViewModel assembly - I have no chances to create DialogView instance; on the other hand, if I will place DialogService in the View assembly, how I should inject it in my ViewModel class?

So, I would recoment look at Advanced MVVM scenarios with Prism Part: Using Interaction Request Objects

As example of this approach:

DialogViewModelBase

public abstract class DialogViewModelBase : ViewModelBase
{
    private ICommand _ok;

    public ICommand Ok
    {
        get { return _ok ?? (_ok = new DelegateCommand(OkExecute, CanOkExecute)); }
    }

    protected virtual bool CanOkExecute()
    {
        return true;
    }

    protected virtual void OkExecute()
    {
        _isSaved = true;
        Close = true;
    }

    private ICommand _cancel;

    public ICommand Cancel
    {
        get 
        {
           return _cancel ?? (_cancel = new DelegateCommand(CancelExecute, CanCancelExecute));
        }
    }

    protected virtual bool CanCancelExecute()
    {
        return true;
    }

    protected virtual void CancelExecute()
    {
        Close = true;
    }

    private bool _isSaved = false;
    public bool IsSaved
    {
        get { return _isSaved; }
    }

    private bool _close = false;

    public bool Close
    {
        get { return _close; }
        set
        {
            _close = value;
            RaisePropertyChanged(() => Close);
        }
    }
}

CreateUserStoryViewModel:

public class CreateUserStoryViewModel : DialogViewModelBase
{
    private string _name = String.Empty;

    public string Name
    {
        get { return _name; }
        set
        {
            _name = value;
            RaisePropertyChanged(() => Name);
        }
    }
}

CreateUserStoryRequest

private InteractionRequest<Notification> _createUserStoryRequest;
public InteractionRequest<Notification> CreateUserStoryRequest
{
    get
    {
        return _createUserStoryRequest ?? (_createUserStoryRequest = new InteractionRequest<Notification>());
    }
}

CreateUserStory Command

private void CreateUserStoryExecute()
{
    CreateUserStoryRequest.Raise(new Notification()
    {
        Content = new CreateUserStoryViewModel(),
        Title = "Create User Story"
    }, 
    notification =>
                 {
                      CreateUserStoryViewModel createUserStoryViewModel =
                               (CreateUserStoryViewModel)notification.Content;
                      if (createUserStoryViewModel.IsSaved)
                      {
                         _domainContext.CreateUserStory(
new UserStory(){ Name = createUserStoryViewModel.Name, });
                      }
                 });
}

XAML:

<!--where xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
          xmlns:ir="clr-namespace:Microsoft.Practices.Prism.Interactivity.InteractionRequest;assembly=Microsoft.Practices.Prism.Interactivity"-->

<i:Interaction.Triggers>
  <ir:InteractionRequestTrigger SourceObject="{Binding CreateUserStoryRequest}">
    <ir:PopupChildWindowAction>
      <ir:PopupChildWindowAction.ChildWindow>
        <view:CreateUserStory />
      </ir:PopupChildWindowAction.ChildWindow>
    </ir:PopupChildWindowAction>
  </ir:InteractionRequestTrigger>
</i:Interaction.Triggers>

Solution 4

As I understood your comment above, the question is not so much about showing the dialogs as about hiding them. There are two ways to solve this problem:

  1. Use standard dialog window to implement the view. This would require to have a loosely coupled way of communication between View and ViewModel so that ViewModel can notify the View that it's ok to close without having a reference to a view.

    There are multiple frameworks exist that would allow to do it - Prism's event aggregators would be one of them. In this scenario View would subscribe to an event (say, MyDialogResultValidated), and on receiving the event it would set the DialogResult accrodingly. ViewModel (in its SaveCommand) would fire the event if validation was successful.

  2. Don't use standard dialog window to implement the view. This would require to have an overlay that would effectively emulate modality.

    In this scenario the Visibility of the View and of the overlay will be bound ViewModel's IsVisible property that would be set accordingly by SaveCommand implementation, or whenever ViewModel needs to show the View.

The first approach would require having a bit of code in code-behind, requires adding global event(s), and (arguably) is less MVVM-ish. The second approach would require implementing (or using someone else's implementation) of the overlay, but won't require having any code in code-behind, won't require having global event(s), and is (arguable) more MVVM-ish.

Share:
37,958
Oll
Author by

Oll

Updated on October 11, 2020

Comments

  • Oll
    Oll over 3 years

    Does anyone have any examples of showing a window dialog using MVVM (Prism)? - for example a configuration settings window when a command is executed.

    All of the examples I've seen use the mediator pattern which is fine, but they also all have a reference to the view in the view model which is not ideal (we're using DataTemplates)

    Thanks