使用服务定位器的MVVM模态对话框

Ani*_*aul 6 wpf modal-dialog mvvm

我正在开发一个遵循MVVM模式的WPF应用程序.要显示模态对话框,我试着按照以下文章建议的方式. http://www.codeproject.com/Articles/36745/Showing-Dialogs-When-Using-the-MVVM-Pattern?fid=1541292&fr=26#xx0xx

但是在这些文章中,我观察到,从MainWindowViewModel调用DialogService接口的ShowDialog方法.

我的申请中的情况略有不同.MainWindow.xaml包含一个用户控件,表示包含按钮Add的ChildView.MainWindowViewModel包含另一个ViewModel,表示与ChildView绑定的ChildVM.ChildVM包含AddCommand,当调用与AddCommand相对应的AddExecute方法时,我需要显示模态对话框.我怎么能做到这一点?

编辑代码

     private Window FindOwnerWindow(object viewModel)
            {
                    FrameworkElement view = null;

        // Windows and UserControls are registered as view.
        // So all the active windows and userControls are contained in views
        foreach (FrameworkElement viewIterator in views)
        {
            // Check whether the view is an Window
            // If the view is an window and dataContext of the window, matches
            // with the viewModel, then set view = viewIterator
            Window viewWindow = viewIterator as Window;
            if (null != viewWindow)
            {
                if (true == ReferenceEquals(viewWindow.DataContext, viewModel))
                {
                    view = viewWindow;
                    break;
                }

            }
            else
            {
                // Check whether the view is an UserControl
                // If the view is an UserControl and Content of the userControl, matches
                // with the viewModel, then set view = userControl
                // In case the view is an user control, then find the Window that contains the
                // user control and set it as owner
                System.Windows.Controls.UserControl userControl = viewIterator as System.Windows.Controls.UserControl;
                if (null != userControl)
                {
                    if (true == ReferenceEquals(userControl.Content, viewModel))
                    {
                        view = userControl;
                        break;
                    }

                }
            }
        }
        if (view == null)
        {
            throw new ArgumentException("Viewmodel is not referenced by any registered View.");
        }

        // Get owner window
        Window owner = view as Window;
        if (owner == null)
        {
            owner = Window.GetWindow(view);
        }

        // Make sure owner window was found
        if (owner == null)
        {
            throw new InvalidOperationException("View is not contained within a Window.");
        }

        return owner;
        }
Run Code Online (Sandbox Code Playgroud)

Mar*_*arc 4

好的,如果我没理解错的话,您想不是从 MainWindowViewModel 而是从另一个 ChildViewModel 打开模式对话框?

查看您链接的 CodeProject 文章的 MainWindowViewModel 的构造函数:

ViewModel 有一个具有以下签名的构造函数:

public MainWindowViewModel(
            IDialogService dialogService,
            IPersonService personService,
            Func<IOpenFileDialog> openFileDialogFactory)
Run Code Online (Sandbox Code Playgroud)

这意味着,为了构建,您需要显示模式对话框的服务、另一个服务(personService)(这里无关紧要)以及用于打开文件的特定对话框的工厂 openFileDialogFactory。

为了使用本文的核心部分服务,实现了一个简单的 ServiceLocator 并定义了一个默认构造函数,该构造函数使用 ServiceLocator 来获取 ViewModel 需要的服务的实例:

public MainWindowViewModel()
            : this(
            ServiceLocator.Resolve<IDialogService>(),
            ServiceLocator.Resolve<IPersonService>(),
            () => ServiceLocator.Resolve<IOpenFileDialog>())
        {}
Run Code Online (Sandbox Code Playgroud)

这是可能的,因为 ServiceLocator 是静态的。或者,您可以使用 ServiceLocator 在构造函数中设置服务的本地字段。上述方法更好,因为如果您不想使用 ServiceLocator,它允许您自己设置服务。

您可以在自己的 ChildViewModel 中执行完全相同的操作。

public ChildViewModel(IDialogService dialogService)
{
    _dialogService = dialogService;
}
Run Code Online (Sandbox Code Playgroud)

创建一个默认构造函数,它使用从 ServiceLocator 解析的服务实例调用上述构造函数:

public ChildViewModel() : this(ServiceLocator.Resolve<IDialogService>()) {}
Run Code Online (Sandbox Code Playgroud)

现在您可以从 ChildViewModel 中的任何位置使用该服务,如下所示:

_dialogService.ShowDialog<WhateverDialog>(this, vmForDialog);
Run Code Online (Sandbox Code Playgroud)

为了找到视图的所有者窗口(它不是视图本身),您需要修改FindOwnerWindowDialogService 的方法来查找视图的父窗口,而不是期望 Window 作为视图本身。您可以使用 VisualTreeHelper 来执行此操作:

private Window FindOwnerWindow(object viewModel)
    {
        var view = views.SingleOrDefault(v => ReferenceEquals(v.DataContext, viewModel));

        if (view == null)
        {
            throw new ArgumentException("Viewmodel is not referenced by any registered View.");
        }

        DependencyObject owner = view;

        // Iterate through parents until a window is found, 
        // if the view is not a window itself
        while (!(owner is Window))
        {
            owner = VisualTreeHelper.GetParent(owner);
            if (owner == null) 
                throw new Exception("No window found owning the view.");
        }

        // Make sure owner window was found
        if (owner == null)
        {
            throw new InvalidOperationException("View is not contained within a Window.");
        }

        return (Window) owner;
    }
Run Code Online (Sandbox Code Playgroud)

不过,您仍然需要注册 UserControl,在 UserControl 上设置附加属性:

<UserControl x:Class="ChildView"
             ...
             Service:DialogService.IsRegisteredView="True">
   ...
 </UserControl>
Run Code Online (Sandbox Code Playgroud)

据我所知,这是可行的。

附加信息:

为了完成同样的事情,我使用 PRISM 框架,该框架提供了许多用于此类解耦、控制反转 (IoC) 和依赖注入 (DI) 的功能。也许您也值得一看。

希望这可以帮助!

编辑以考虑该评论。