MVVM在保持视图和视图模型解耦的同时打开窗口

时间:2019-04-16 15:40:07

标签: c# mvvm prism

我第一次尝试使用MVVM模式,但是我在打开视图的同时仍在努力使它们与视图模型分离。我正在使用DialogService类(下面的IDialog.cs),该类是YouTube上MVVM教程的一部分。只要从具有DialogService实例的MainWindow中访问DialogService即可正常工作。

问题是我需要从我的TradeView中打开多个TradeManagerViewModel,其中没有DialogService的实例。我无法创建DialogService的另一个实例,因为我需要为我创建的每个实例注册所有的View / ViewModel映射。我无法使用DialogService中的MainWindowViewModel实例,因为我的TradeMangerViewModel没有对我的MainWindowViewModel实例的引用。在主窗口视图模型中,我无法使public readonly IDialogService dialogService;静态,因为那样我就无法分配在dialogService构造函数中传递的MainWindowViewModel参数。

我唯一想到的另一种方法是创建一个单独的单例类,该类包含DialogService的实例,以便可以从两个视图模型(以及我尚未编写的未来模型)中访问同一实例。 。但是我也阅读了有关单例类的许多不同意见,其中大多数建议您根本不需要使用它们。因此,我发现该观点有例外吗?还是我可以/应该采用其他方法吗?

App.xaml.cs(此处的更改也来自YouTube视频)

public partial class App : Application
{
    protected override void OnStartup(StartupEventArgs e)
    {
        IDialogService dialogService = new DialogService(MainWindow);

        dialogService.Register<TradeViewModel, TradeView>();
        dialogService.Register<TradeManagerViewModel, TradeManager>();

        var viewModel = new MainWindowViewModel(dialogService);

        base.OnStartup(e);
    }
}

IDialog.cs

/// <summary>
/// Allows Windows/Dialogs to be opened and closed without coupling the View to the ViewModel
/// </summary>
public interface IDialog
{
    object DataContext { get; set; }
    bool? DialogResult { get; set; }
    Window Owner { get; set; }
    void Close();
    bool? ShowDialog();
}

/// <summary>
/// Registers a dictionary of View Models to the the correct Views allowing the correct View to be displayed when an instance of a View Model is instantiated
/// </summary>
public interface IDialogService
{
    void Register<TViewModel, TView>() where TViewModel : IDialogRequestClose
                                       where TView : IDialog;

    bool? ShowDailog<TViewModel>(TViewModel viewModel) where TViewModel : IDialogRequestClose;

}

/// <summary>
/// Creates an Event Handler which handles close requests for the dialog
/// </summary>
public interface IDialogRequestClose
{
    event EventHandler<DialogCloseRequestedEventArgs> CloseRequested;
}

public class DialogCloseRequestedEventArgs : EventArgs
{
    public DialogCloseRequestedEventArgs(bool? dialogResult)
    {
        DialogResult = dialogResult;
    }

    public bool? DialogResult { get; }
}

public class DialogService : IDialogService
{
    private readonly Window owner;

    /// <summary>
    /// Initialises the DialogService and sets its owner
    /// </summary>
    /// <param name="owner">The Window which will own the DialogService. The main window of the application will probably be the best owner.</param>
    public DialogService(Window owner)
    {
        this.owner = owner;
        Mappings = new Dictionary<Type, Type>();
    }

    public IDictionary<Type, Type> Mappings { get; } //Used to store which type of View should be used with each ViewModel

    /// <summary>
    /// Register which View should be used with a ViewModel
    /// </summary>
    /// <typeparam name="TViewModel">Type of ViewModel</typeparam>
    /// <typeparam name="TView">Type of View</typeparam>
    public void Register<TViewModel, TView>()
        where TViewModel : IDialogRequestClose
        where TView : IDialog
    {
        if (Mappings.ContainsKey(typeof(TViewModel))) //If a mapping already exists for this type of ViewModel
        {
            throw new ArgumentException($"Type {typeof(TViewModel)} is already mapped to type {typeof(TView)}");
        }

        Mappings.Add(typeof(TViewModel), typeof(TView)); //Otherwise create a new mapping
    }

    /// <summary>
    /// Shows the correct View for the given ViewModel and subscribes to the close request handler
    /// </summary>
    /// <typeparam name="TViewModel"></typeparam>
    /// <param name="viewModel">ViewModel which you want to open the mapped View for</param>
    /// <returns>Returns bool dialog result</returns>
    public bool? ShowDailog<TViewModel>(TViewModel viewModel) where TViewModel : IDialogRequestClose
    {
        Type viewType = Mappings[typeof(TViewModel)]; //Get the type of View associated with this type of ViewModel from the Mappings Dictionary

        IDialog dialog = (IDialog)Activator.CreateInstance(viewType); //Create an instance of the mapped view

        EventHandler<DialogCloseRequestedEventArgs> handler = null;

        // When the handler is called, unsubscribe from the event as we no longer need to listen to it once the View has been closed
        handler = (sender, e) =>
        {
            viewModel.CloseRequested -= handler;

            if (e.DialogResult.HasValue)
            {
                dialog.DialogResult = e.DialogResult;
            } else
            {
                dialog.Close();
            }
        };

        //Subscribe to the CloseRequested event
        viewModel.CloseRequested += handler;

        dialog.DataContext = viewModel;
        dialog.Owner = owner;

        return dialog.ShowDialog();
    }
}

MainWindowViewModel.cs

internal class MainWindowViewModel
{

    public readonly IDialogService dialogService;

    public MainWindowViewModel(IDialogService dialogService)
    {
        this.dialogService = dialogService;

        //Load settings etc. removed.

        //This works here, but dialogService isn't accessible in TradeManagerViewModel:
        var tradeManagerViewModel = new TradeManagerViewModel(filePath);
        bool? result = this.dialogService.ShowDialog(tradeManagerViewModel);
    }
}

3 个答案:

答案 0 :(得分:3)

通常,解耦的解决方案是使用依赖注入/控制反转。您可以使用任何DI容器(作为Unity)。

此外,您可以使用Prism之类的MVVM框架,它可以帮助您创建松散耦合且可维护的整个应用程序。

答案 1 :(得分:1)

您会从其他人的建议中受益于IoC容器,但我不认为您应该从Prism开始。从小开始,使用MVVM Light中的IoC容器,有许多示例向您展示如何使用该库编写应用程序。

您还可以看一下MVVM Dialogs的示例,其中有很多示例都可以在IoC容器中设置对话框服务。

答案 2 :(得分:1)

  

但是我也阅读了有关单例类的许多不同观点,其中大多数表明您根本不需要使用它们。

那是完全错误的。实际上,单例确实有助于使彼此之间不认识的实例进行通信。我会使用only make those classes a singleton that need to be one之类的弱势语句,但是完全没有理由完全避免单例。

相关问题