6

I am learning WPF MVVM and want to open a new window on a button click from a main window.

I know each View has to have an equivalent ViewModel and one of the basic principles of MVVM is that the ViewModel must not know anything about the View.

So please can anybody provide me a simple clean example that does not violate any MVVM principles on how to create two Views and two ViewModels that have the following functionality:

Show a new view by clicking a button from a main View.

whitefang1993
  • 1,666
  • 3
  • 16
  • 27
  • When it comes to opening new window for MVVM, it becomes tricky. One of the main reasons is that `DataTemplate` does not allow you to define a `Window` derived class inside. There are a lot of articles on the net giving many different methods. I would suggest try reading them up first. Each method has its pros and cons, and generally they don't follow 100% MVVM principles too. – Jai May 18 '16 at 08:36

5 Answers5

4

You can create a separate service for launching views as dialog so that it can be used in a generic way across the application. And will inject this service to the ViewModel via Constructor which wants to launch any dialog.

public interface IDialogWindowService<T>
{
    void Show();
    void ShowDialog();
}

public class DialogWindowService<T> : IDialogWindowService<T> where T : Window
{
    public void Show()
    {
        container.Resolve<T>().Show();
    }

    public void ShowDialog()
    {
        container.Resolve<T>().ShowDialog();
    }
}

Now just inject this service to the respective ViewModel.

public class YourViewModel
{
    //commands
    public ICommand someCommand { get; set; }

    private IDialogWindowService<BookingView> _dialogService;
    public YourViewModel(IDialogWindowService<YourView > dialogService)
    {
        _dialogService = dialogService
        someCommand = new RelayCommand(someCommandDoJob, () => true);
    }

    public void someCommandDoJob(object obj)
    {
        //Since you want to launch this view as dialog you can set its datacontext in its own constructor.    
        _dialogService.ShowDialog();
    }
}

OR

you can use DataTemplates to change view. It allows to dynamically switch Views depending on the ViewModel:

<Window>
   <Window.Resources>
      <DataTemplate DataType="{x:Type ViewModelA}">
         <localControls:ViewAUserControl/>
      </DataTemplate>
      <DataTemplate DataType="{x:Type ViewModelB}">
         <localControls:ViewBUserControl/>
      </DataTemplate>
   <Window.Resources>
  <ContentPresenter Content="{Binding CurrentView}"/>
</Window>

If Window.DataContext is an instance of ViewModelA, then ViewA will be displayed and

Window.DataContext is an instance of ViewModelB, then ViewB will be displayed.

The best example I've ever seen and read it is made by Rachel Lim. See the example.

alexheslop1
  • 135
  • 1
  • 11
StepUp
  • 36,391
  • 15
  • 88
  • 148
  • 3
    You are referencing a View in ViewModel: private IDialogWindowService _dialogService; isn't this a violation of MVVM? – whitefang1993 May 18 '16 at 10:29
  • @whitefang1993 could you show where I am referencing a view in viewModel? – StepUp May 18 '16 at 10:30
  • 2
    In the YourViewModel class the "IDialogWindowService" type reference "_dialogService" is used for the type BookingView. Lets say you have the BookingView in another project don't you have to add a reference to that project in order to use a reference of "IDialogWindowService"? So If you have two projects, one for the ViewModels and one for the Views you have to refer the Views project in the ViewModels project so you can use the BookingView in YourViewModel. If I misunderstood something please correct me. I am just trying to clarify some things. – whitefang1993 May 18 '16 at 10:51
  • 1
    @whitefang1993 yeap, you are right. I forgot about this. See this example. http://stackoverflow.com/questions/16652501/open-a-new-window-in-mvvm – StepUp May 18 '16 at 10:59
2

Depending on your usage, there isn't anything wrong with opening a view from the view's code-behind. It's still view code after all.

MyView view = new MyView();
view.Show();

Otherwise, if you need to open a window from the ViewModel or using a ICommand, then you can look at the "Open Windows and Dialogs in MVVM" library I wrote on GitHub. This will demonstrate how to open a Window by clicking on a button using the MVVM design pattern.

Mike Eason
  • 9,525
  • 2
  • 38
  • 63
  • I'm also new to WPF and MVVM. But as far as I understand, this is a very bad approach. The reason is that your ViewModel becomes non unit-testable when you do this. If you pull the whole ViewModel folder and throw it into a new project without the existence of View, then your code is not going to compile and run. – Jai May 18 '16 at 09:28
  • I'm not sure if you fully understood my answer here. By code-behind, I mean the **view's** code behind, not in the view model code. – Mike Eason May 18 '16 at 11:12
  • Sorry, my bad, I thought the code is from ViewModel. – Jai May 19 '16 at 01:47
0

What I've done with some success in the past is to create what is basically a View Factory, that constructs a view and assigns it a viewmodel. This gives me a one-stop shop to do the stiching for the views, much like you would to using IoC.

There may be advantages and disadvantages to this, so I'd be curious to learn if there are other/better ways, but so far, this is the practice I've found least painful.

Pedro G. Dias
  • 3,162
  • 1
  • 18
  • 30
0

To be a more pure implementation of MVVM you can use a factory or have an interface-driven controller written for each view that handles a) what to show and b) how to bind it to the data. So for the controller : FormAlpha has FormAlphaViewModel and FormAlpha.VPS. The interface is perfect for this since every implementation is different.

So create a standard that each view had an interface-driven class that ends with vps - or visual presentation service. And you want to trigger FormAlpha on that button click. You would use reflection or factory to load FormAlpha.vps as an IVisualPresentor and call IVisualPrentor.Display(parms);

Each VPS has the job of loading a specific form, binding the databinding with the view model associated with that form (which not many solutions provided), and the first parameter always has a required parameter of Show() or ShowDialog().

And...of course...the first thing your viewmodel gets are the parameters passed. So VPS will break out those parameters and confirm that the correct parameters were passed. In this way (from your example) you have a function that the button represents and wants to deploy. The VPS handles the data and visuals needed to accomplish that. The caller just knows that the VPS will hand whatever that function is. Now I just rattled the code below from memory so it isn't perfect. But presents the thoughts I'm talking about above.

public interface IVisualPresentor
{ void Display(params object[] parms) };

public class FormAlpha.VPS : IVisualPresentor
{  
     public void Display(params object[] parms)
     {
       //validate parms if needed and cast to type specific data
       //for example it needs session data parms[1] and customer parms[2]
       var form = new FormAlpha();
       var model = new FormAlphaViewModel( sessionData, customer );
       form.DataBinding = model;
       if ((bool)parms[0])
          form.Show(); 
       else 
          form.ShowDialog();
    }
 }
SASS_Shooter
  • 2,186
  • 19
  • 30
-2

Let's say that Form1 is your main form,and Form2 is the one you want to open: You can use this code(insert it into Form1):

Form2 frm = new Form2();
frm.Show();

Also if you want to close the main form you can use this.Close();(it will close current form). Furthermore if you want just to open Form2 but still Form1 to be the main one you can use:

Form2 frm = new Form2();
frm.Show();
this.Focus();
Dabuleanu Catalin
  • 59
  • 1
  • 1
  • 15