Using MVVM show new window and get updates data

2019-04-14 11:30发布

问题:

I'm working on a WPF MVVM application. I'm showing some data in a datagrid. I've two buttons to Add and Edit the selected record. I've data in ViewModel and I've to show another window (view) and make sure that ViewModels should have no information about views. Where should I create its view and viewmodel? How to get the data back and update datagrid? How can I achieve this in MVVM? We have not yet decided to use any framework, so I've to create my own interface.

回答1:

Note: This ended up being quite a long answer - please ask me if anything is unclear

The implementation of dialog windows is a contentious issue in MVVM designs, and different people use different approaches.

Like you, I've decided not to use any framework and implement most things by hand. When it comes to dialog windows, I choose to be pragmatic about my implementation of MVVM, by launching the Dialog Window from inside my ViewModel. Also, I allow each Dialog ViewModel to have a reference to the Window it is displayed in, so it can close it when appropriate (details below). This breaks some of the strict MVVM "rules", but it gets the job done.

The main downside of this is that it might break unit testing if you are testing something that goes through a dialog. However, you can go a long way without running into that problem and it has not bothered me yet.

I've built up a bit of a library of dialog ViewModels which I can easily extend. It's way too much code to post here, but I'll show you the highlights.

Base ViewModel for Dialogs

Each of my dialog windows has a ViewModel that inherits from DialogViewModelBase, which is similiar to my regular ViewModelBase in that it provides support for INotifyPropertyChanged etc. The interesting part is this public method, which I call from wherever to launch the Dialog:

/// <summary>
/// Creates window instance for this dialog viewmodel and displays it, getting the dialog result.
/// </summary>
public void ShowDialogWindow()
{
    // This is a property of the DialogViewModelBase class - thus, each DialogViewModel holds a reference to its own DialogWindow:
    this.DialogWindow = new Dialogs.Views.DialogWindow();
    // Tell the DialogWindow to display this ViewModel:
    this.DialogWindow.DataContext = this;
    // Launch the Window, using a method of the Window baseclass, that only returns when the window is closed:
    this.DialogWindow.ShowDialog();
}

Window launched in the above method will close when its Window.DialogResult property is set. This is why the DialogWindow is a property of the DialogViewModelBase class - when the subclassing dialog ViewModel wants to close the dialog window, it simply sets the result:

protected void CloseDialogWithResult(bool dialogWindowResult)
{
    // Setting this property automatically closes the dialog window:
    this.DialogWindow.DialogResult = dialogWindowResult;
}

Host Window for Dialog Views

The Dialogs.Views.DialogWindow class that the ShowDialogWindow method instantiates is defined in XAML and is a subclass of Window. It has two important features. The first is that it's primary content element is simply a ContentControl that binds to the current context. This allows me to define different Views for different subclasses of DialogViewModelBase, and the DialogWindow will host the corresponding View based on the type of the context:

<ContentControl Content="{Binding}" /> <!-- In reality this is inside a border etc but its simplified here for demonstration -->

The second important feature of the DialogWindow XAML is that it defines which dialog Views go with which dialog ViewModels. Here is a sample:

<Window.Resources>
    <!-- DEFAULT ViewModel-View TEMPLATES -->

    <DataTemplate DataType="{x:Type dialogs:YesNoMessageBoxDialogViewModel}">
        <views:MessageBoxView />
    </DataTemplate>

    <DataTemplate DataType="{x:Type dialogs:ErrorDialogViewModel}">
        <views:ErrorDialogView/>            
    </DataTemplate>

</Window.Resources>

What all this does, is that I can define dialogs as subclasses to DialogViewModelBase and implement a View for each, and then tell DialogWindow which View its ContentControl must show for which dialog ViewModel.

Launching a Dialog and getting results

Below is a sample from one of my application ViewModels, in which I launch a Dialog Window that allows the user to select an Asset Type for creation:

public void CreateNewAsset()
{
    // Instantiate desired Dialog ViewModel:
    Dialogs.NewAssetTypeSelectionDialogViewModel dialog = new Dialogs.NewAssetTypeSelectionDialogViewModel();

    // Launch Dialog by calling method on Dialog base class:
    dialog.ShowDialogWindow();

    // Execution will halt here until the Dialog window closes...

    // The user's selection is stored in a property on the dialog ViewModel, and can now be retrieved:
    CalculatorBase.AssetTypeEnum newAssetType = dialog.AssetType;

    switch (newAssetType)
    {
        // Do stuff based on user's selection...
    }
}

PS: I should really write a blog entry about this - when I do, I will post the link here, as the blog entry will probably have more complete code samples.



回答2:

It depends how you are handling the data. I will assume that changes made in the popup window can be accepted only when user clicks something like save in other case they should be discarded. So firstly, I would suggest using MVC approach as controller is perfect for such tasks. You build viewmodels in it, assign them o views and show the views. VM's simply keeps data and commands, commands execute methods are kept in controller. In other words you have singleton class which manages your VM's and views. You should check out Prism framework. It offers great things like view regios where you can inject different user controls on the runtime, commanding and MVC layering out of the box alongside IOC and DI patterns.