29

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

Oll
  • 945
  • 1
  • 14
  • 23

6 Answers6

25

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.

H.B.
  • 166,899
  • 29
  • 327
  • 400
Cameron MacFarland
  • 70,676
  • 20
  • 104
  • 133
  • Hi Cameron! How does the view inform the service whether OK or Cancel was pressed? – Louis Rhys Mar 28 '11 at 07:09
  • The standard way. The code implementation of `ShowDialog` would just find the View it needs to display, and then call `ShowDialog` on that View, and pass the result back. – Cameron MacFarland Mar 28 '11 at 07:13
  • How do property changes inside the IDIalogViewModel trigger the evaluation of the CanExecute of the commands bound to the [OK] & [Cancel] buttons on the standard dialog? InvalidateRequerySuggested? – jan Apr 07 '11 at 08:46
13

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...

H.B.
  • 166,899
  • 29
  • 327
  • 400
Grokys
  • 16,228
  • 14
  • 69
  • 101
  • Thanks for the answer. Could you clarify the below scenario please. Say you display a view model in the DialogWindow with Save and Cancel commands. When the user clicks Save on the view (bound to the SaveCommand) you would want to maybe run some validation and save the configuration, only if this is successful would the dialog close. I can't seem to get my head around how the VM would set the DialogResult of the view (therefore closing the dialog). Thanks again. – Oll Nov 04 '09 at 10:23
  • There are many ways of getting the VM to interact with the view without breaking the abstraction. For instance, the dialog view model base class typically has some base properties and methods to make showing dialogs a little easier (I typically have a interface for a dialog viewmodel that includes things like a method that is meant to run on view load, as well as the dialog result, commit/abort commands, etc). An easy way to get the viewmodel to tell the view to close is to expose a property (CanClose for instance). – Egor Nov 10 '09 at 20:32
  • Then create a corresponding dependency property on your dialog, and when the datacontext changes set a binding between the two - that way you can handle any logic in your bound property's changed event handler. Then again, it's even easier to simply expose a "close" event on your dialog viewmodel, and in the dialog window data context changed handler subscribe to this event, the handler can assign appropriate dialog result and close the window. – Egor Nov 10 '09 at 20:43
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>
Vladimir Dorokhov
  • 3,784
  • 2
  • 23
  • 26
  • Vladimir: I have tried the approach but it is not working for me.Can u elaborate what is _domainContext. My scenario is open a modal dialog which has a textbox,label and pdfviewer. I have to change the datacontext of ModalDialog [ie. passing those values as a viewmodel] according to the button(trigger) click. Any idea or reference would be helpful... Thanks – C-va Jun 03 '13 at 13:30
  • _domainContext in my sample is sort of repository. It's just example and you can replace it with WCF service call or saving to the disk. This sample will work for your scenario. You need replace CreateUserStoryViewModel with your own ModalDialogViewModel with properties Title, Text etc. and bind these properties to your ModalDialogView's Label and PdfViewer (ModalDialogView should be used instead of my CreateUserStory view). – Vladimir Dorokhov Jun 03 '13 at 13:47
  • This doesn't appear to work with WPF. The element doesn't exist. However it does in Silverlight. – Gareth Oates May 26 '15 at 09:01
  • @GarethOates, you are right. Please, look at this http://stackoverflow.com/questions/18596168/prism-popupchildwindowaction-in-desktop-dll-missing – Vladimir Dorokhov May 26 '15 at 10:32
2

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.

Community
  • 1
  • 1
PL.
  • 2,195
  • 12
  • 10
0

You might be interested in the following sample application:

http://compositeextensions.codeplex.com

It uses Prism2 with the PresentationModel (aka MVVM) pattern. The sample application contains a modal dialog.

jbe
  • 6,976
  • 1
  • 43
  • 34
0

It isn't Prism, but this MVVM demo has an Options Dialog that's fully MVVM.

Scott Whitlock
  • 13,739
  • 7
  • 65
  • 114