Show dialog with MVVM Light toolkit
Asked Answered
I

4

20

I have a ViewModel that needs to show a modal window (using ShowDialog()) on a button click. The ViewModel catches the click command, but I don't want to do window.ShowDialog() within my ViewModel. I know there is a DialogMessage in MVVM Light, but that is used to show message boxes, not WPF modal windows.

Any ideas on how to do this?

Insectarium answered 13/4, 2011 at 4:37 Comment(1)
see #16993933Abaca
M
22

You should use Messenger class. On the View register a message to show window, and then when you need to show it call Send method of Messenger class.

You can do something like this:

    //do this in the code-behind file of your View
    Messenger.Default.Register<string>(this, ShowWindow);
    
    private void ShowWindow(string message)
    {
        // your logic here
    }
    
    // In the ViewModel
    Messenger.Default.Send(“Some text”);
Manthei answered 13/4, 2011 at 5:35 Comment(0)
W
14

This is what I use for custom dialogs with the MVVM-Light Toolkit.

First, define these four classes somewhere in your application. The MessageBase class is part of the toolkit.

public class ShowChildWindowMessage : MessageBase { }
public class HideChildWindowMessage : MessageBase { }
public class DisplaySomeContentMessage : MessageBase { }
public class DisplaySomeOtherContentMessage : MessageBase { }

Second, you need a "child" window control. Create a XAML file with the following content:

<Window x:Class="ChildWindowView"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        DataContext="{Binding Path=ChildWindowBinding, Source={StaticResource Locator}}"
        Title="{Binding Path=CurrentContent.DisplayName}"
        MinWidth="300" MinHeight="125" SizeToContent="WidthAndHeight"
        ShowInTaskbar="False" WindowState="Normal" ResizeMode="NoResize"
        WindowStartupLocation="CenterOwner" SnapsToDevicePixels="True">

    <Grid>
        <ContentPresenter Content="{Binding Path=CurrentContent}" />
    </Grid>
</Window>

Then add the following to the code-behind of this XAML file:

public partial class ChildWindowView : Window
{
    public ChildWindowView(Window owner)
    {
        InitializeComponent();
        Owner = owner;

        Closing += (s, e) => 
        {
            // window reused so just hide
            e.Cancel = true;
            Messenger.Default.Send(new HideChildWindowMessage());
        };
    }

}

Third, add the following to the code-behind of your MainWindow.xaml file:

public partial class MainWindowView : Window
{
    private ChildWindowView m_childWindowView;

    public MainWindowView()
    {
        InitializeComponent();
        Closing += (s, e) => ViewModelLocator.CleanUp();
        Loaded += (s, e) =>
        {
            m_childWindowView = new ChildWindowView(this);
        };

        Messenger.Default.Register<ShowChildWindowMessage>(this, (msg) => m_childWindowView.ShowDialog());
        Messenger.Default.Register<HideChildWindowMessage>(this, (msg) => m_childWindowView.Hide());
    }
}

Fourth, define the following view model:

public class ChildWindowVM : ViewModelBase
{
    private ViewModelBase m_currentContent;
    public ViewModelBase CurrentContent
    {
        get { return m_currentContent; }
        set
        {
            m_currentContent = value;
            RaisePropertyChanged("CurrentContent");

            if (m_currentContent != null)
            {
                Messenger.Default.Send(new ShowChildWindowMessage());
            }
        }
    }

    public ChildWindowVM()
    {
        Messenger.Default.Register<DisplaySomeContentMessage>(this, (msg) => CurrentContent = ViewModelLocator.SomeContentVm);
        Messenger.Default.Register<DisplaySomeOtherContentMessage>(this, (msg) => CurrentContent = ViewModelLocator.SomeOtherContentVm);
    }
}

Fifth, you create XAML files and view models for the content you want to display in your custom dialog. In this example, my content view models were named SomeContent and SomeOtherContent. You would replace these with what ever you want, of course.

Finally, in order for this to work you must bind your content view models to their respective XAML files by adding the following to your application resources:

<DataTemplate DataType="{x:Type viewmodels:SomeContentVM}">
    <views:SomeContentView/>
</DataTemplate>

<DataTemplate DataType="{x:Type viewmodels:SomeOtherContentVM}">
    <views:SomeOtherContentView/>
</DataTemplate>

Once you get all this set up it is straightforward to add new content (XAML and view models) that can be displayed in your child window. To display the content, simply call the appropriate message using the Messenger class:

Messenger.Default.Send(new DisplaySomeContentMessage ());

Let me know if I need to clarify any part of this for you.

Wakerly answered 4/10, 2012 at 17:41 Comment(7)
but how do you return the dialog results with such approach?Andre
The "dialog result" is handled in the view model referenced by the CurrentContent property in ChildWindowVM. For example, your SomeContentView.xaml would have buttons bound to some RelayCommand in your SomeContentVM class. When a button is clicked, the related command executes the method assigned to it. In other words, it's not really the same as a dialog result of a MessageBox, because this the ChildWindow view and view model do not care about the behavior of what's being displayed. It's just a window wrapper for the real view and view model referenced by the CurrentContent property.Wakerly
Interesting! I decided to have a list of child windows in my MainWindow.xaml (Dictionary<Guid,Window>). In the above method ViewModels are created twice so another method is to have tighter coupling of ViewModels by adding DataContext="{Binding Content, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContentPresenter}}}" to the DataTemplatesCher
@sky-dev, how are the ViewModels being "created twice"?Wakerly
bugged87 after a couple of days more in this code I think that you are right. My ViewModels were being created twice because I had been using a View-first MVVM pattern in which the ViewModel was being created the second time in the ChildWindow's content control. I have since updated things to be ViewModel first, and I'm actually enjoying that a lot more. It seems to really save on the overhead of creating new views, because I can specify them in the same ResourceDictionary if I choose to. I'm still using a variant of your ChildWindow, which allows multiple child windows to be open at once.Cher
Very witty, thanks. Some notes for followers: 1) .ShowDialog() does not return a dialog result if window is hidden, so we should handle that manually. 2) Child views should be created (and owner attached) in Loaded, otherwise app won't close on MainWindow closing.Beshrew
If you do end up using this solution and try to show the same dialog/view with a new view model back to back you will run into the issue where it doesn't appear to be refreshing. Take a look here at the solution I had to use to fix this: refresh dialog fixMaximilian
S
0

For all of you who want a really simple solution and are okay with not-100%-clean-MVVM:
I wanted to open a connect dialog from my main window and did the following

First i gave my MainWindow a name:

<Window x:Name="MainWindow">

Then i created a Command in my MainWindowViewModel:

public ICommand AddInterfaceCommand
{
    get
    {
        return new RelayCommand<Window>((parentWindow) =>
        {
            var wizard = new ConnectionWizard();
            wizard.Owner = parentWindow;
            wizard.ShowDialog();
        }
    }
}

I bound my Button on the MainWindow to the Command and passed the window itself (the parent window for the dialog):

<Button Command="{Binding AddInterfaceCommand}" CommandParameter="{Binding ElementName=MainWindow}">Add interface</Button>

That's all.

Only caveat: Getting return values from the Viewmodel of the Dialog could be difficult. I don't need that feature.

Shiftless answered 31/10, 2014 at 17:30 Comment(0)
P
0

You can define an interface and its implementation as follows. And ofcourse with dependency injection container, you have to do someting like this.

NInjectKernel.Bind<IMessageBoxService>().To<MessageBoxService>();

Your ViewModel will look something like this.

    private IMessageBoxService _MBService;
    public DropboxSettingsViewModel(IDropboxService dbService, IMessageBoxService mbService)
    {
        if (dbService == null)
            throw new ArgumentNullException("IDropboxService is null");

        _DropboxService = dbService;

        if (mbService == null)
            throw new ArgumentNullException("MessageBoxService is null");

        _MBService = mbService;

    }

Your click command execute method would be as follows.

    private void ConfigureDropboxExecute(object obj)
    {

        _MBService.Show("Error Occured Authenticating dropbox", "Dropbox Authentication", MessageBoxButton.OK, MessageBoxImage.Error, MessageBoxResult.OK);

    }


public interface  IMessageBoxService
{
    MessageBoxResult Show(string messageBoxText);
    MessageBoxResult Show(string messageBoxText, string caption);
    MessageBoxResult Show(Window owner, string messageBoxText);
    MessageBoxResult Show(string messageBoxText, string caption, MessageBoxButton button);
    MessageBoxResult Show(Window owner, string messageBoxText, string caption);
    MessageBoxResult Show(string messageBoxText, string caption, MessageBoxButton button, MessageBoxImage icon);
    MessageBoxResult Show(Window owner, string messageBoxText, string caption, MessageBoxButton button);
    MessageBoxResult Show(string messageBoxText, string caption, MessageBoxButton button, MessageBoxImage icon, MessageBoxResult defaultResult);
    MessageBoxResult Show(Window owner, string messageBoxText, string caption, MessageBoxButton button, MessageBoxImage icon);
    MessageBoxResult Show(string messageBoxText, string caption, MessageBoxButton button, MessageBoxImage icon, MessageBoxResult defaultResult, MessageBoxOptions options);
    MessageBoxResult Show(Window owner, string messageBoxText, string caption, MessageBoxButton button, MessageBoxImage icon, MessageBoxResult defaultResult);
    MessageBoxResult Show(Window owner, string messageBoxText, string caption, MessageBoxButton button, MessageBoxImage icon, MessageBoxResult defaultResult, MessageBoxOptions options);
}

using System.Windows;

public class MessageBoxService : IMessageBoxService
{
    public MessageBoxResult Show(string messageBoxText)
    {
        return MessageBox.Show(messageBoxText);
    }

    public MessageBoxResult Show(Window owner, string messageBoxText)
    {
        return MessageBox.Show(owner, messageBoxText);
    }

    public MessageBoxResult Show(string messageBoxText, string caption)
    {
        return MessageBox.Show(messageBoxText, caption);
    }

    public MessageBoxResult Show(Window owner, string messageBoxText, string caption)
    {
        return MessageBox.Show(owner, messageBoxText, caption);
    }

    public MessageBoxResult Show(string messageBoxText, string caption, MessageBoxButton button)
    {
        return MessageBox.Show(messageBoxText, caption, button);
    }

    public MessageBoxResult Show(Window owner, string messageBoxText, string caption, MessageBoxButton button)
    {
        return MessageBox.Show(owner, messageBoxText, caption, button);
    }

    public MessageBoxResult Show(string messageBoxText, string caption, MessageBoxButton button, MessageBoxImage icon)
    {
        return MessageBox.Show(messageBoxText, caption, button, icon);
    }

    public MessageBoxResult Show(Window owner, string messageBoxText, string caption, MessageBoxButton button, MessageBoxImage icon)
    {
        return MessageBox.Show(owner, messageBoxText, caption, button, icon);
    }

    public MessageBoxResult Show(string messageBoxText, string caption, MessageBoxButton button, MessageBoxImage icon, MessageBoxResult defaultResult)
    {
        return MessageBox.Show(messageBoxText, caption, button, icon, defaultResult);
    }

    public MessageBoxResult Show(Window owner, string messageBoxText, string caption, MessageBoxButton button, MessageBoxImage icon, MessageBoxResult defaultResult)
    {
        return MessageBox.Show(owner, messageBoxText, caption, button, icon, defaultResult);
    }

    public MessageBoxResult Show(string messageBoxText, string caption, MessageBoxButton button, MessageBoxImage icon, MessageBoxResult defaultResult, MessageBoxOptions options)
    {
        return MessageBox.Show(messageBoxText, caption, button, icon, defaultResult, options);
    }

    public MessageBoxResult Show(Window owner, string messageBoxText, string caption, MessageBoxButton button, MessageBoxImage icon, MessageBoxResult defaultResult, MessageBoxOptions options)
    {
        return MessageBox.Show(owner, messageBoxText, caption, button, icon, defaultResult, options);
    }
}
Polis answered 3/10, 2016 at 11:43 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.