Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

WPF Open a new View from the ViewModel

Tags:

c#

mvvm

wpf

xaml

This is my first WPF-MVVM application, and this is my structure:

  1. One project with my app.xaml to open the application and override the OnStartup to resolve the MainWindow. (I did that due to the references);
  2. One project for my Views;
  3. One project for my ViewModels;
  4. One project for my Model.

And I have de following problem: I'm on the MainWindowView and I click on a button to show another view. How am I supposed to do to open this another view from my MainWindowViewModel whereas my View Project has reference with the ViewModel Project, and I can't reference the ViewModel Project with the View Project?

By the way, I'm using Unity for the dependency injection.

So, could you help me?

like image 549
Guilherme Oliveira Avatar asked Dec 02 '12 16:12

Guilherme Oliveira


2 Answers

There are several approaches to that.

You can define a dialog/navigation/window service interface, defined in the ViewModels project. You will need to decide how the ViewModels will express which window they want to open. I generally use an IDialogViewModel interface, which some of my ViewModels implement, and pass an instance of the ViewModel to the service, but you can use an enum, string, whatever you want, so your implementation can map to the real window which will be opened.

For example:

public interface IDialogService
{
    bool? ShowDialog(object dialogViewModel); 
}

ViewModels that want to open new Windows would receive an instance of that service and use it to express the intention of opening a Window. In your Views project, you would define a type which implements your service interface, with the real logic behind opening the Window.

Following the example:

public class DialogService : IDialogService
{
    private Stack<Window> windowStack = new Stack<Window>();


    public DialogService(Window root)
    {
        this.windowStack.Push(root);
    }

    public bool? ShowDialog(object dialogViewModel)
    {
        Window dialog = MapWindow(dialogViewModel); 
        dialog.DataContext = dialogViewModel;
        dialog.Owner = this.windowStack.Peek();

        this.windowStack.Push(dialog);

        bool? result;

        try
        {
            result = dialog.ShowDialog();
        }
        finally
        {
            this.windowStack.Pop();
        }

        return result;
    }

}

Your main project will be responsible for creating and injecting the dialog service in the ViewModels who need it. In the example, the App would create a new dialog service instance passing the MainWindow to it.

A similar approach to do it would be using some form of the messaging pattern (link1 link2 ). In addition, if you want something simple you can also make your ViewModels raise events when they want to open Windows and let the Views subscribe to them.

EDIT

The complete solution that I use in my apps a generally a bit more complex, but the idea is basically that. I have a base DialogWindow, which expects a ViewModel which implements an IDialogViewModel interface as DataContext. This interface abstracts some functionalities you expect in dialog, like accept/cancel commands as well as a closed event so you can also close the window from the ViewModel. The DialogWindow consists basically in a ContentPresenter which Content property is bound to the DataContext and hooks the close event when the DataContext is changed (and a few other things).

Each "dialog" consists in an IDialogViewModel and an associated View (UserControl). To map them, I just declare implicit DataTemplates in the resources of the App. In the code I've shown, the only difference would be there wouldn't be a method MapWindow, the window instance would always be a DialogWindow.

I use an additional trick to reuse layout elements between dialogs. On approach is to include them in the DialogWindow (accept/cancel buttons, etc). I like to keep the DialogWindow clean (so I can use it event to "non-dialog" dialogs). I declare a template for a ContentControl with the common interface elements, and when I declare a View-ViewModel mapping template, I wrap the View with a ContentControl with my "dialog template" applied. You can then have as much "Master templates" for your DialogWindow as you want (like a "wizard like" one, for example).

like image 95
Arthur Nunes Avatar answered Oct 26 '22 15:10

Arthur Nunes


Straight forward approach

If I understand you correctly MainWindowView is resolved through Unity when the app starts, which resolves its dependency on MainWindowViewModel?

If that is the flow you are using I would suggest continuing on the same path and letting the MainWindowView handle the opening of the new view via a simple click handler for the button. In this handler you can then resolve the new view, which would resolve that view's view model and then you're back in MVVM land for the new view as well.

That solution is straight forward and would work perfectly fine for most smaller applications.

Heavier approach for more complex apps

If you don't want that kind of view-first flow I would suggest introducing some kind of controller/presenter that coordinates the views and view models. The presenter is responsible for making decisions about if/when to actually open/close views and so on.

This is a quite heavy abstraction though, which is more suitable for more complex applications, so make sure you actually get enough benefit out of it to justify the added abstraction/complexity.

Here is a code sample of what this approach might look like:

public partial class App
{
    protected override void OnStartup(StartupEventArgs e)
    {
        var container = new UnityContainer();

        container.RegisterType<IMainView, MainWindow>();
        container.RegisterType<ISecondView, SecondWindow>();
        container.RegisterType<IMainPresenter, MainPresenter>();
        container.RegisterType<ISecondPresenter, SecondPresenter>();

        var presenter = container.Resolve<IMainPresenter>();

        presenter.ShowView();
    }
}

public interface IMainPresenter
{
    void ShowView();
    void OpenSecondView();
}

public interface ISecondPresenter
{
    void ShowView();
}

public interface ISecondView
{
    void Show();
    SecondViewModel ViewModel { get; set; }
}

public interface IMainView
{
    void Show();
    MainViewModel ViewModel { get; set; }
}

public class MainPresenter : IMainPresenter
{
    private readonly IMainView _mainView;
    private readonly ISecondPresenter _secondPresenter;

    public MainPresenter(IMainView mainView, ISecondPresenter secondPresenter)
    {
        _mainView = mainView;
        _secondPresenter = secondPresenter;
    }

    public void ShowView()
    {
        // Could be resolved through Unity just as well
        _mainView.ViewModel = new MainViewModel(this);

        _mainView.Show();
    }

    public void OpenSecondView()
    {
        _secondPresenter.ShowView();
    }
}

public class SecondPresenter : ISecondPresenter
{
    private readonly ISecondView _secondView;

    public SecondPresenter(ISecondView secondView)
    {
        _secondView = secondView;
    }

    public void ShowView()
    {
        // Could be resolved through Unity just as well
        _secondView.ViewModel = new SecondViewModel();
        _secondView.Show();
    }
}

public class MainViewModel
{
    public MainViewModel(MainPresenter mainPresenter)
    {
        OpenSecondViewCommand = new DelegateCommand(mainPresenter.OpenSecondView);
    }

    public DelegateCommand OpenSecondViewCommand { get; set; }
}

public class SecondViewModel
{
}

<!-- MainWindow.xaml -->
<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <Button Command="{Binding OpenSecondViewCommand}" Content="Open second view" />
    </Grid>
</Window>

<!-- SecondWindow.xaml -->
<Window x:Class="WpfApplication1.SecondWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="SecondWindow" Height="350" Width="525">
    <Grid>
        <TextBlock>Second view</TextBlock>
    </Grid>
</Window>

This article presents a similar solution to what I've used in production before.

like image 37
Erik Öjebo Avatar answered Oct 26 '22 13:10

Erik Öjebo