Opening new window in MVVM WPF

C#WpfMvvm

C# Problem Overview


I have a Button and I bind this button to a command in ViewModel say OpenWindowCommand. When I click on the button I want to open a new window. But creating a window instance and showing a window from view model is a violation of MVVM. I have created interface like

interface IWindowService
{
    void showWindow(object dataContext);
}

and WindowService implements this interface like

class WindowService : IWindowService
{
    public void showWindow(object dataContext)
    {
        ChildWindow window=new ChildWindow();
        window.DataContext=dataContext;
        window.Show();
    }
}

In this class I have specified ChildWindow. So this class is tightly coupled with showing ChildWindow. When I want to show another window, I have to implement another class with the same interface and logic. How can I make this class generic so that I can just pass an instance of any window and the class will be able to open any window?

I am not using any built MVVM frameworks. I have read many articles on StackOverflow but I could not found any solution for this.

C# Solutions


Solution 1 - C#

You say "creating window instance and showing window from view model is violation of MVVM". This is correct.

You are now trying to create an interface that takes a type of view specified by the VM. This is just as much of a violation. You may have abstracted away the creation logic behind an interface, but you are still requesting view creations from within the VM.

VM's should only care about creating VM's. If you really need a new window to host the new VM, then provide an interface as you have done, but one that does NOT take a view. Why do you need the view? Most (VM first) MVVM projects use implicit datatemplates to associate a view with a particular VM. The VM knows nothing about them.

Like this:

class WindowService:IWindowService
{
    public void ShowWindow(object viewModel)
    {
        var win = new Window();
        win.Content = viewModel;
        win.Show();
    }
}

Obviously you need to make sure you have your VM->View implicit templates set up in app.xaml for this to work. This is just standard VM first MVVM.

eg:

<Application x:Class="My.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:vm="clr-namespace:My.App.ViewModels"
             xmlns:vw="clr-namespace:My.App.Views"
             StartupUri="MainWindow.xaml">
    <Application.Resources>

        <DataTemplate DataType="{x:Type vm:MyVM}">
            <vw:MyView/>
        </DataTemplate>
        
    </Application.Resources>
</Application>

Solution 2 - C#

One possibility is to have this:

class WindowService:IWindowService
{
 public void showWindow<T>(object DataContext) where T: Window, new() 
 {
  ChildWindow window=new T();
  window.Datacontext=DataContext;
  window.show();
 }
}

Then you can just go something like:

windowService.showWindow<Window3>(windowThreeDataContext);

For more information on the new constraint, see http://msdn.microsoft.com/en-gb/library/sd2w2ew5.aspx

Note: the new() constraint only works where the window will have a parameterless constructor (but I imagine this shouldn't be a problem in this case!) In a more general situation, see https://stackoverflow.com/questions/731452/create-instance-of-generic-type for possibilities.

Solution 3 - C#

You could write a function like this:

class ViewManager
{
    void ShowView<T>(ViewModelBase viewModel)
        where T : ViewBase, new()
    {
        T view = new T();
        view.DataContext = viewModel;
        view.Show(); // or something similar
    }
}

abstract class ViewModelBase
{
    public void ShowView(string viewName, object viewModel)
    {
        MessageBus.Post(
            new Message 
            {
                Action = "ShowView",
                ViewName = viewName,
                ViewModel = viewModel 
            });
    }
}

Make sure the ViewBase has a DataContext property. (You could inherit UserControl)

In general I would make some kind of message bus and have a ViewManager listen for messages asking for a view. ViewModels would send a message asking for a view to be shown and the data to show. The ViewManager would then use the code above.

To prevent the calling ViewModel to know about the View types you could pass a string/logical name of the view to the ViewManager and have the ViewManager translate the logical name into a type.

Solution 4 - C#

use a contentpresenter in your Window where you bind your DataConext to. And then define a Datatemplate for your DataContext so wpf can render your DataContext. something similar to my DialogWindow Service

so all you need is your one ChildWindow with a ContentPresenter:

<Window x:Class="ChildWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
WindowStartupLocation="CenterOwner" SizeToContent="WidthAndHeight">
<ContentPresenter Content="{Binding .}">

</ContentPresenter>
</Window>

Solution 5 - C#

I find the accepted solution very useful, but when trying it practically, I found that it lacks the ability to make the UserControl (the View that results from the VM -> View mapping) dock within the hosting window in order to occupy the whole area provided by it. So I extended the solution to include this ability:

public Window CreateWindowHostingViewModel(object viewModel, bool sizeToContent)
{
   ContentControl contentUI = new ContentControl();
   contentUI.Content = viewModel;
   DockPanel dockPanel = new DockPanel();
   dockPanel.Children.Add(contentUI);
   Window hostWindow = new Window();
   hostWindow.Content = dockPanel;

   if (sizeToContent)
       hostWindow.SizeToContent = SizeToContent.WidthAndHeight;

   return hostWindow;
}

The trick here is using a DockPanel to host the view converted from the VM.

Then you use the previous method as follows, if you want the size of the window to match the size of its contents:

var win = CreateWindowHostingViewModel(true, viewModel)
win.Title = "Window Title";
win.Show();

or as follows if you have a fixed size for the window:

var win = CreateWindowHostingViewModel(false, viewModel)
win.Title = "Window Title";
win.Width = 500;
win.Height = 300;
win.Show();

Solution 6 - C#

Maybe you could pass the window type.

Try using Activator.CreateInstance().

See the following question: https://stackoverflow.com/questions/981330/instantiate-an-object-with-a-runtime-determined-type.

Solution by chakrit:

// determine type here
var type = typeof(MyClass);

// create an object of the type
var obj = (MyClass)Activator.CreateInstance(type);

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionDT sawantView Question on Stackoverflow
Solution 1 - C#GazTheDestroyerView Answer on Stackoverflow
Solution 2 - C#David EView Answer on Stackoverflow
Solution 3 - C#EmondView Answer on Stackoverflow
Solution 4 - C#blindmeisView Answer on Stackoverflow
Solution 5 - C#Ghareeb FalaziView Answer on Stackoverflow
Solution 6 - C#HellinView Answer on Stackoverflow