48

I am using MVVM Light toolkit in my WPF application. I would like to know what is the best approach for opening a new window from an existing window. I have got this MainViewModel, which is responsible for MainWindow of my application. Now in the MainView, on a button click, I would like to open a second window on top of it. I have got RelayCommmand binded to the Button's Command. In the RelayCommand's method, I can create a new window object and simply call Show(), something like this:

var view2 = new view2()
view2.Show()

but I don't think the ViewModel should be responsible for creating the new view2 object. I have read this post WPF MVVM Get Parent from VIEW MODEL where Bugnion has suggested to pass message to the view1 from the viewmodel1 and then view1 should create the new view2. But I am not sure what does he actually mean by passing the message to the view1? How should the view1 handle the message? In it's code behind or what?

Regards, Nabeel

Community
  • 1
  • 1
nabeelfarid
  • 4,156
  • 5
  • 42
  • 60
  • see http://stackoverflow.com/questions/16993433/mvvm-light-wpf-binding-multiple-instances-of-a-window-to-a-viewmodel/16994523#16994523 – reggaeguitar Apr 24 '14 at 22:07

6 Answers6

60

Passing a message from ViewModel1 to View1 means to use the messaging capabilities in the MVVM Light Toolkit.

For example, your ViewModel1 could have a command called ShowView2Command, then it would send a message to display the view.

public class ViewModel1 : ViewModelBase
{
    public RelayCommand ShowView2Command { private set; get; }

    public ViewModel1() : base()
    {
        ShowView2Command = new RelayCommand(ShowView2CommandExecute);
    }

    public void ShowView2CommandExecute()
    {
        Messenger.Default.Send(new NotificationMessage("ShowView2"));
    }
}

View1 would register to receive messages in its code behind and display View2 when it receives the correct message.

public partial class View1 : UserControl
{
    public View1()
    {
        InitializeComponent();
        Messenger.Default.Register<NotificationMessage>(this, NotificationMessageReceived);
    }

    private void NotificationMessageReceived(NotificationMessage msg)
    {
        if (msg.Notification == "ShowView2")
        {
            var view2 = new view2();
            view2.Show();
        }
    }
}
Matt Casto
  • 2,150
  • 1
  • 22
  • 32
  • Thanks for the response Matt. Are there any other approaches/best practices for opening new views in mvvm apart from using messaging ? – nabeelfarid Aug 03 '10 at 08:16
  • 11
    Another approach that I've seen people using is to have a service style of class that is used to open the view. Your ViewModel would work with the interface of this service to display the ChildWindow, MessageBox, or whatever. This is a particular favorite of those who want zero code in their view's code-behind. Also, its a bit more testable since you can mock the service and assert that the method to display your view was called. – Matt Casto Aug 03 '10 at 13:03
  • 2
    yeah I have seen people talking about it too. But what I do not understand in this approach is that when you are opening a child window from a view model using some service lets say IDialogService.OpenChild(), how would you setup the owner of the child window, as the viewmodel calling IDialogService.OpenChild() does not know or have a reference to its own view? – nabeelfarid Aug 04 '10 at 11:57
  • You don't have to set an owner for a ChildWindow, or at least I've never had to. If you must have an owner, you could probably get the App's root visual and use that. – Matt Casto Aug 04 '10 at 12:41
  • 2
    Well there are various reasons why one would need to set the ownership of a child window. The ownsership relationship enforces certain behaviors, including with respect to minimizing, maximizing, and restoring etc.(http://msdn.microsoft.com/en-us/library/system.windows.window.owner.aspx). As for using the RootVisual, that again means sending message to the view, because rootvisual will be accessible in codebehind, not in viewmodel? – nabeelfarid Aug 10 '10 at 15:43
  • You can call App.Current.RootVisual from anywhere. However, this does add a dependency into your ViewModel class that should be encapsulated for unit testing. – Matt Casto Aug 10 '10 at 16:57
  • 2
    This is pretty cool, but at the same time, it reminds me of MFC, circa 1997 using Message Pumps... – Mike Caron Jul 15 '13 at 14:45
  • This solution seems to be good separation of concerns. I used it in my scenario where I want to Show SomeView using RelayCommand from ViewModel. I need a hint since I used that solution and NotificationMessageReceived is event not triggered. Firstly I have added to ViewModel RelayCommand wihch sends notification message, that message is registered under view in constructor. Anyway it is weird since SomeView is nowhere instantiated. Could you advised me? – komizo Aug 29 '14 at 08:31
  • I used another solution from question 5829413. That solution looks good. I used it - works but I am not sure if it is a good solution, since I had to add another non static constructor to App.cs. Could you give me a hint where should I register correctly that message which is responsible for showing View? App is a good solution? Or maybe Locator, but locator? I will be grateful for advise. – komizo Aug 29 '14 at 08:50
  • Is View2 a page or user-control? – user3260977 Nov 16 '16 at 18:31
  • As @MattCasto has stated, this can be accomplished via a messenger or view service. Laurent Bugnion (MVVM Light Author) has written an MSDN article on Messenger and View Services in MVVM here: https://msdn.microsoft.com/en-us/magazine/jj694937.aspx – Derek W Apr 27 '17 at 17:54
  • In case anyone is wondering - both `Messenger` and `NotificationMessage` can be found in `GalaSoft.MvvmLight.Messaging` namespace. – itsho Aug 29 '17 at 18:23
  • @MattCasto: This works pretty good, thank you! How can I ensure that the correct DataContext is set when I call a ChildView (DataContext = ChildViewModel) from the MainView (DataContext = MainViewModel) – br0ken.pipe Feb 23 '19 at 16:07
  • The concept boils down too what Prism is doing I think but should View open another View? I think this is too much logic in the view. MVVM starts to be unbearable. – ed22 Mar 16 '19 at 17:50
4

Why do you go this route? Its simple. If you replace your button with a toggleButton, or a hyperlink, or any other number of button-like controls, you don't need to update your "code behind" - its a basic principle of the MVVM pattern. In your new toggleButton (or whatever), you still end up binding to the same exact Command.

For example, I'm creating a project for a client who wants to have 2 UI's - one is going to be fundamentally different in every way, in terms of presentation. Horizontal tabs vs Vertical RadPanelBar (think Accordion) for navigation. Both of these views can point to the same viewModel - when a user clicks the Work Order tab in View 1, it fires the same "WorkOrderCommand" that's fired in the Work Order Header in the panel bar.

In a code-behind model, you'd have to code two separate events. Here you only have to code one.

Furthermore, it allows a designer using Blend to create any layout they want. As long as they have the hooks (EventToCommand control) in place, myself (as a developer) couldn't care less what the final product looks like.

Loose coupling is incredibly powerful.

Scott Silvi
  • 3,059
  • 5
  • 40
  • 63
3

You can do in this way like you need to create some events and register those in view and call these in view model.and open that pop up window.

Like This example

public class Mainclass : MainView
{
    public delegate abc RegisterPopUp(abc A);
    public RegisterPopUp POpUpEvent ;

    public RelayCommand ShowCommand { private set; get; }  


    public void ShowCommand() 
    { 
        ShowCommand("Your parameter");
    } 
}

inside the view MainView mn=new MainView();

Register the event here like thake mn.POpUpEvent += than click on tab button double time

and in registers popup method right the code for opening the pop up window.

mychalvlcek
  • 3,956
  • 1
  • 19
  • 34
Hoshiyar
  • 31
  • 1
2

Unless I am missing the point here - if I were to use the code behind, then why not directly implement button_click event and open the second view?

What Bugnion seems to be suggesting is view1 -> button click -> relay command -> viewmodel1 -> message -> view1 -> view1.cs -> open view 2.

You are going to sacrifice testability anyhow by writing code-behind, so why take such a long route?

Pratz
  • 309
  • 1
  • 3
  • 5
  • 4
    the long route will make sure that : when you are testing your viewmodel, you can atleast test that a message/request has been broadcasted to open new view. You can wrap the message request code in an IDialogService to make it mockable during testing. – nabeelfarid Aug 25 '10 at 09:23
  • 2
    I agree with Pratz, it's a bit crazy to take such a long route. – Vincent Oct 28 '10 at 17:53
  • 2
    The code behind method is fine for a small app with a small number of windows/views. The added complexity does seem overkill if you have a main window that just opens an occasional 2nd window just to display some details. If the app gets big, the code behind won't scale well and testing will suffer. – srock Sep 05 '13 at 14:37
2

You can abstract the view specific features into services using generic interface. In the view layer you can provide concrete instances of these services and build view models using the IoC container and Dependency Injection technique.

In your case you can build an interface IWindowManager or something similar which has the required method. This can be implmented in your view layer. I wrote a small blog post recently demonstrating how to abstract the dialog behaviour out of view model. Similar apporach can be used for any user interface related service like Navigation, MessageBoxes etc.

This link might be helpful for you http://nileshgule.blogspot.com/2011/05/silverlight-use-dialogservice-to.html

Many people also use the approach of firing events from view models which are subscribed on the view.cs file and from there the MessageBox or any other UI related action is performed. I personally like the approach of injecting services because then you can provide multiple implementations of the same service. A simple example would be how navigation is handled in Silverlight and Windows Phone 7 applications. You can use the same view model but inject different implementations of the Navigation service based on the application type.

bkaid
  • 51,465
  • 22
  • 112
  • 128
Nilesh Gule
  • 1,511
  • 12
  • 13
0

I find the best way to approach this, is opening and closing the window from the ViewModel. As this link suggests,

  1. Create a DialogCloser class
    public static class DialogCloser
    {
        public static readonly DependencyProperty DialogResultProperty = DependencyProperty.RegisterAttached("DialogResult", typeof(bool?), typeof(DialogCloser), new PropertyMetadata(DialogResultChanged));

        private static void DialogResultChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var window = d as Window;
            if (window != null) window.Close();
        }

        public static void SetDialogResult(Window target, bool? value)
        {
            target.SetValue(DialogResultProperty, value);
        }
    }
  1. Create a Base ViewModel inheriting from GalaSoft.MvvmLight.ViewModelBase with there additional members. Once done, use this viewmodel as base for other viewmodels.
    bool? _closeWindowFlag;
    public bool? CloseWindowFlag
    {
        get { return _closeWindowFlag; }
        set
        {
            _closeWindowFlag = value;
            RaisePropertyChanged("CloseWindowFlag");
        }
    }

    public virtual void CloseWindow(bool? result = true)
    {
        Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Background, 
        new Action(() =>
        {
            CloseWindowFlag = CloseWindowFlag == null ? true : !CloseWindowFlag;
        }));
    }
  1. In the view, Bind the DialogCloser.DialogResult dependency property with the CloseWindowFlag property in the base viewmodel.

Then you can open/close/hide the window from the viewmodel.

Touhid Alam
  • 343
  • 1
  • 5