Showing a modal dialog and getting results

wingerse

I have a static WindowService class which helps me to create new windows and modal dialogs. So far, what I have is this:

/// <summary>
/// Opens a new window of type <paramref name="newWindowType"/> and closes the <paramref name="oldWindow"/>
/// </summary>
/// <param name="oldWindow">The window which should be closed (Usually the current open window)</param>
/// <param name="newWindowType">The type of the new window to open</param>
public static void ShowNewWindow(Window oldWindow, Type newWindowType)
{
    ((Window)Activator.CreateInstance(newWindowType)).Show();
    oldWindow.Close();
}

My viewmodel raises an event and the view is subscribed to it. In the event handler in the view, it calls WindowService.ShowNewWindow(this,The type here). This works fine.
My modal dialog creating method will also work in a similar way. The only difference is that the information will be returned to the view (At the event handler) so the view will have to pass that information to the view model in code explicitly. This violates mvvm pattern and I don't know how to make the viewmodel wait for the view to return the value after the event is raised.
Is there a better way of doing this?

Mike Eason

Ah, this ol' chestnut.

There are many different variations on how to achieve this, however here's my two cents.

The main ideas here are to ensure that your View and View Model do not know about each other, therefore your View should not subscribe to an event in your View Model, and your View Model should not directly call your service and provide a view Type.


Don't use events, use Commands instead

My recommendation would be to use ICommand implementations instead of relying on a static service class, for the reason that your class will always have a dependency to this service, and also as soon as you send the view Type to this service, then the MVVM pattern is lost.

So, firstly, we need some kind of command which will open a window of a given Type, here's what I have come up with:

public class OpenWindowCommand : ICommand
{
    public bool CanExecute(object parameter)
    {
        TypeInfo p = (TypeInfo)parameter;

        return p.BaseType == typeof(Window);
    }

    public event EventHandler CanExecuteChanged
    {
        add { CommandManager.RequerySuggested += value; }
        remove { CommandManager.RequerySuggested -= value; }
    }

    public void Execute(object parameter)
    {
        if (parameter == null)
            throw new ArgumentNullException("TargetWindowType");

        //Get the type.
        TypeInfo p = (TypeInfo)parameter;
        Type t = p.BaseType;

        if (p.BaseType != typeof(Window))
            throw new InvalidOperationException("parameter is not a Window type");

        //Create the window.
        Window wnd = Activator.CreateInstance(t) as Window;

        OpenWindow(wnd);
    }

    protected virtual void OpenWindow(Window wnd)
    {
        wnd.Show();
    }
}

The class inherits from ICommand and specifies the implementation which accepts a Type, which represents the desired View that we want to open. Notice I have marked a method as virtual, I'll explain that part in a moment.

Here's how we can make use of this command in our View Model:

public class MainWindowViewModel
{
    public OpenWindowCommand OpenWindowCommand { get; private set; }

    public MainWindowViewModel()
    {
        OpenWindowCommand = new OpenWindowCommand();
    }

    ...
}

Now we've created the command, we simply need to bind a Button to it:

<Button Content="Open Window"
        Command="{Binding OpenWindowCommand}"
        CommandParameter="{x:Type local:MyWindow}"/>

One thing to note here is that I am using x:Type as the CommandParameter, this is the Window that will be created when this command gets executed.


But what about a dialog?

What we achieved above is only half of the requirement, we now need something that will display a dialog and output the result to our View Model, this isn't so tricky as we have most of what we need already in our existing OpenWindowCommand.

First, we need to create the command:

public class ShowDialogCommand : OpenWindowCommand
{
    private Action _PreOpenDialogAction;
    private Action<bool?> _PostOpenDialogAction;

    public ShowDialogCommand(Action<bool?> postDialogAction)
    {
        if (postDialogAction == null)
            throw new ArgumentNullException("postDialogAction");

        _PostOpenDialogAction = postDialogAction;
    }

    public ShowDialogCommand(Action<bool?> postDialogAction, Action preDialogAction)
        : this(postDialogAction)
    {
        if (preDialogAction == null)
            throw new ArgumentNullException("preDialogAction");

        _PreOpenDialogAction = preDialogAction;
    }

    protected override void OpenWindow(System.Windows.Window wnd)
    {
        //If there is a pre dialog action then invoke that.
        if (_PreOpenDialogAction != null)
            _PreOpenDialogAction();

        //Show the dialog
        bool? result = wnd.ShowDialog();

        //Invoke the post open dialog action.
        _PostOpenDialogAction(result);
    }
}

We're making use of our OpenWindowCommand by inheriting from it and using it's implementation instead of having to copy all of it into our new class. The command takes an Action which is a reference to a method in your View Model, you have the option of defining an action before or after (or both) a dialog is displayed.

The next step is to change our View Model so it creates this new command:

public class MainWindowViewModel
{
    public OpenWindowCommand OpenWindowCommand { get; private set; }
    public ShowDialogCommand ShowDialogCommand { get; private set; }

    public MainWindowViewModel()
    {
        OpenWindowCommand = new OpenWindowCommand();
        ShowDialogCommand = new ShowDialogCommand(PostOpenDialog);
    }

    public void PreOpenDialog()
    {
        throw new NotImplementedException();
    }

    public void PostOpenDialog(bool? dialogResult)
    {
        throw new NotImplementedException();
    }
}

The usage of this command is practically the same as before, but it just references a different command:

<Button Content="Open Window"
        Command="{Binding ShowDialogCommand}"
        CommandParameter="{x:Type local:MyWindow}"/>

And there you have it, everything is loosely coupled, the only real dependencies here are that your View Model depends on your ICommand classes.


Some final words

The ICommand classes that I have created act as a controller between the View and the View Model to ensure that they do not know about each other, and keeps the MVVM pattern enforced.

Like I said at the beginning of this answer, there are many ways of which this can be achieved, however I hope you are now a little more enlightened.

Collected from the Internet

Please contact [email protected] to delete if infringement.

edited at
0

Comments

0 comments
Login to comment

Related

From Dev

Modal dialog not showing on PrimeFaces 5

From Dev

TeamCity with showing a modal dialog box

From Dev

Showing multiple templates within a single modal dialog

From Dev

Bootstrap modal-dialog is not getting display

From Dev

Bootstrap modal-dialog is not getting display

From Dev

Who should be responsible for showing a modal dialog with a log in form?

From Dev

How to hide the body scroll bar when showing scroll is in modal dialog?

From Dev

Showing modal dialog when navigating away from a tab in TabStrip

From Dev

Modal Dialog Box not showing in ASP.NET web

From Dev

Protractor modal dialog returns multiple results for ng-repeater

From Dev

Modal RCP Dialog not modal

From Dev

Core Data modal entity objects creation getting unexpected results

From Dev

HTML5 Modal Dialog is showing shortly on page load, how can i stop this?

From Dev

How to change bootstrap modal dialog so that it has no animation while showing but fade outs slowly?

From Dev

href in modal dialog in bootstrap

From Dev

Passing parameters to modal dialog

From Dev

Validation in bootstrap modal dialog

From Java

Angular 2.0 and Modal Dialog

From Dev

Modal dialog to confirm delete

From Dev

Jquery Dialog modal issues

From Dev

Elm modal dialog box

From Dev

Displaying a Modal Dialog in AngularJS

From Dev

xpage Modal Dialog and sessionScopes

From Dev

Closing and resetting a modal dialog

From Dev

Pass value to dialog modal

From Dev

Editing a modal (dialog) submission

From Dev

Like Modal Dialog android

From Dev

How to move modal dialog

From Dev

Displaying modal login dialog