Opening New Window in Mvvm Wpf

Opening new window in MVVM WPF

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>

What is the recommended way to open a new window using MVVM in WPF

Yes, VMs should communicate with Views utilizing the Events that Views can subscribe to...

In VM:

public event EventHandler<NotificationEventArgs<string>> DisplayOptionsNotice;  

In View:

private readonly MainViewModel mvm;
...
mvm = DataContext as MainViewModel;
mvm.DisplayOptionsNotice += DisplayOptionsWindow;
...
private void DisplayOptionsWindow(object sender, NotificationEventArgs<string> e)
{
...
optionsWindow = new OptionsWindow { Owner = this };
optionsWindow.ShowDialog();
...
}

Open a new Window in MVVM

There are two problems you need to solve with this type of application.

Firstly, you do not want to have the View-Model creating and displaying UI components directly. One of the motivations for using MVVM is to introduce test-ability in to your View-Model, and having this class pop up new windows makes this class harder to test.

The second problem you need to solve is how to resolve the dependencies in your application, or in this instance – how to you “hook up” the View-Model to the corresponding View? A maintainable solution to this latter problem is given by the use of a DI container. A very good reference to this subject is given by Mark Seemann’s Dependency Injection in .NET. He actually also discusses how to solve the first problem too!

To solve the former problem, you need to introduce a layer of indirection to your code, to make the View-Model not dependent on a concrete implementation of creating a new window. A very simple example is given in the code below:

public class ViewModel
{
private readonly IWindowFactory m_windowFactory;
private ICommand m_openNewWindow;

public ViewModel(IWindowFactory windowFactory)
{
m_windowFactory = windowFactory;

/**
* Would need to assign value to m_openNewWindow here, and associate the DoOpenWindow method
* to the execution of the command.
* */
m_openNewWindow = null;
}

public void DoOpenNewWindow()
{
m_windowFactory.CreateNewWindow();
}

public ICommand OpenNewWindow { get { return m_openNewWindow; } }
}

public interface IWindowFactory
{
void CreateNewWindow();
}

public class ProductionWindowFactory: IWindowFactory
{

#region Implementation of INewWindowFactory

public void CreateNewWindow()
{
NewWindow window = new NewWindow
{
DataContext = new NewWindowViewModel()
};
window.Show();
}

#endregion
}

Note that you take an implementation of IWindowFactory in the constructor of your View-Model, and it is to this object that the creation of the new window is delegated to. This allows you to substitute the production implementation for a different one during testing.

Opening child window in WPF MVVM pattern - correct solution?

In the context of MVVM a dialog is a module of the View component. Following the rules of MVVM, the View Model component is not allowed to handle controls or implement UI related logic. This means, a class of the View Model is also not allowed to use another class that does handle controls or implements UI logic, as such a class would be part of the View component.

Controls must always be instantiated and handled in the View component.

The definition of a design pattern requires that a pattern must be independent of any language, compiler or platform in order to qualify.

Since code-behind is a pure language feature (and therefore a compiler feature), code-behind can't violate any design pattern.

Code-behind i.e. a partial class, is a crucial part of WPF: not everything can be implemented in XAML. The conclusion that if you can't implement it in XAML, dependency properties or complex logic in general for example, it must be in the View Model is very wrong.

In fact most view related framework code is written in C#. XAML is primarily meant to layout the UI: it visually reflects the tree structure of the UI and excels C# in terms of readability in this context. Also some tasks are easier using XAML, like creating a DataTemplate for example. This are only reasons to prefer XAML over C# when writing UI related code. XAML can never replace C#.

The solution to your problem is to show the dialog from code-behind e.g. by implementing a click handler.

To add more flexibility, the following example makes use of a RoutedCommand to replace a click handler.

This way, the dialog can be shown from any control that is child of the control that defines the corresponding CommandBinding (in this case the MainWindow):

MainWindow.xaml.cs

public partial class MainWindow : Window
{
public static RoutedUICommand ShowAddProductDialogCommand { get; } = new RoutedUICommand(
"Show the product dialog",
nameof(ShowAddProductDialogCommand),
typeof(MainWindow));

private IDialogService DialogService { get; }

public MainWindow(IDialogService dialogService)
{
InitializeComponent();

this.DialogService = dialogService;
var dialogCommandBinding = new CommandBinding(ShowDialogCommand, ExecuteShowDialogCommand, CanExecuteShowDialogCommand);
this.CommandBindings.Add(dialogCommandBinding);
}

private void ExecuteShowDialogCommand(object sender, ExecutedRoutedEventArgs e)
=> this.DialogService.ShowAddProductDialog();

private void CanExecuteShowDialogCommand(object sender, CanExecuteRoutedEventArgs e)
=> e.CanExecute = true;
}

MainWindow.xaml

<Window>
<local:ProductListView />
</Window>

ProductListView.xaml

<UserControl>
<Button Content="Show Dialog"
Command="{x:Static local:MainWindow.ShowAddProductDialogCommand}" />
</UserControl>

IDialogService.cs

public interface IDialogService
{
void ShowAddProductDialog();

// Add more methods - one for each dialog.
// Each method knows how to configure and show the dialog window.
}

DialogService.cs

public class DialogService : IDialogService
{
private Func<IProductAddViewModel> AddProductDialogViewModelFactory { get; }

// Inject a factory for each dialog/ContentTemplate to create new view models for each dialog.
// Add more factories - one for each dialog/dialog view model.
public DialogService(Func<IProductAddViewModel> addProductDialogViewModelFactory)
{
this.AddProductDialogViewModelFactory = addProductDialogViewModelFactory;
}

public void ShowAddProductDialog()
{
IProductAddViewModel dialogDataContext = this.AddProductDialogViewModelFactory.Invoke();
var dialog = new DialogWindow()
{
DataContext = dialogDataContext,
Content = dialogDataContext
};

dialog.ShowDialog();
}
}

App.xaml.cs

// Register the factory for the DialogService
services.AddSingleton<Func<IProductAddViewModel>>(serviceProvider => serviceProvider.GetService<IProductAddViewModel>);

Opening new window from ViewModel

Yes, there are a lot of additional codes for MVVM. Building a command that independent of Views is usually for unit testing, such that the command and ViewModel can be unit tested without involving UI components.

However, if the "command" is just opening a window, it is not worth to create a command, and unit test the command to see if the GenericViewRequested is really fired(you can even check if the correct _specificViewModel is returned). The codes are far more complicated and just little value is added. Just open the window in View's button click event handler and it is fine.

Opening new window in a MVVM WPF Class Libary

What I do is have a xaml resource file in each of my class libraries, and programatically merge these into the main application resources.

Say the class library project assembly name is "MyProject.Client" and it contains the resource file \Views\Resources\LibraryStyles.xaml. The code to merge this would look like:

Application.Current.Resources.MergedDictionaries.Add(
new ResourceDictionary
{
Source = new Uri("pack://application:,,,/MyProject.Client;component/Views/Resources/LibraryStyles.xaml",
UriKind.RelativeOrAbsolute)
});

MVVM show new window from VM when seperated projects

  • Define the IWindowService interface in the model project.
  • Reference the model project from the view model project.
  • Implement the IWindowService in the WPF application (view) project.

The button should then be bound to an ICommand that uses IWindowService to open the window. Something like this:

public class MainWindowViewModel
{
private readonly IWindowService _windowService;

public MainWindowViewModel(IWindowService windowService)
{
_windowService = windowService;
AddProfile = new DelegateCommand(() =>
{
_windowService.OpenProfileWindow(new AddProfileViewModel());
});
}

public ICommand AddProfile { get; }
}

public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new MainWindowViewModel(new WindowService());
}
}

public class WindowService : IWindowService
{
public void OpenProfileWindow(AddProfileViewModel vm)
{
AddProfileWindow win = new AddProfileWindow();
win.DataContext = vm;
win.Show();
}
}


Related Topics



Leave a reply



Submit