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

6
我正在开发一个遵循MVVM模式的WPF应用程序。为了显示模态对话框,我正在尝试按照以下文章所建议的方式进行操作。 http://www.codeproject.com/Articles/36745/Showing-Dialogs-When-Using-the-MVVM-Pattern?fid=1541292&fr=26#xx0xx 但在这些文章中,我观察到DialogService接口的ShowDialog方法是从MainWindowViewModel中调用的。
我的应用程序情况略有不同。 MainWindow.xaml包含一个名为ChildView的用户控件,其中包含一个按钮Add。 MainWindowViewModel包含另一个名为ChildVM的ViewModel,该ViewModel与ChildView绑定。 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;
        }

嗨@Anirban,跟着这篇文章 http://www.codeproject.com/Articles/332615/WPF-Master-Details-MVVM-Application 学习一下模态对话框的使用吧。我曾经用过这篇文章来帮助我创建我的应用程序。希望这能有所帮助! - greg
方法与我提供的链接相同。 - Anirban Paul
1
这看起来很好,但我认为将ViewModel放在UserControl的内容中有点误解了UserControl的作用。这不是UserControl的真正意义所在。为什么不使用ContentControl呢?但我并没有真正的见解。如果对您有效,我会考虑设置DataContext而不是内容。 - Marc
2个回答

4

好的,如果我理解正确,您想从不同的ChildViewModel而不是MainWindowViewModel打开模态对话框?

看一下您链接的CodeProject文章中的MainWindowViewModel的构造函数:

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

public MainWindowViewModel(
            IDialogService dialogService,
            IPersonService personService,
            Func<IOpenFileDialog> openFileDialogFactory)

这意味着在构建过程中,您需要使用显示模态对话框的服务,另一个不相关的服务(personService),以及一个特定的对话框工厂来打开文件openFileDialogFactory。
为了使用这个核心部分的服务,在这篇文章中实现了一个简单的ServiceLocator并定义了一个默认构造函数,该构造函数使用ServiceLocator来获取ViewModel所需的服务实例:
public MainWindowViewModel()
            : this(
            ServiceLocator.Resolve<IDialogService>(),
            ServiceLocator.Resolve<IPersonService>(),
            () => ServiceLocator.Resolve<IOpenFileDialog>())
        {}

这是可能的,因为ServiceLocator是静态的。或者,您可以使用ServiceLocator在构造函数中设置服务的本地字段。以上方法更好,因为如果您不想使用ServiceLocator,它允许您自己设置服务。
您可以在自己的ChildViewModel中完全相同地操作。
public ChildViewModel(IDialogService dialogService)
{
    _dialogService = dialogService;
}

创建一个默认构造函数,该函数从ServiceLocator中解析出服务实例,并调用上述构造函数:
public ChildViewModel() : this(ServiceLocator.Resolve<IDialogService>()) {}

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

_dialogService.ShowDialog<WhateverDialog>(this, vmForDialog);

为了找到您的视图的拥有者窗口,该视图本身不是一个视图,您需要修改DialogService的FindOwnerWindow方法来查找视图的父窗口,而不是期望将窗口作为视图本身。您可以使用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;
    }

尽管如此,您仍需要注册UserControl,并在UserControl上设置附加属性:

<UserControl x:Class="ChildView"
             ...
             Service:DialogService.IsRegisteredView="True">
   ...
 </UserControl>

据我所知,这个方法是可行的。

附加信息:

为了实现同样的功能,我使用 PRISM 框架,它提供了很多用于解耦、控制反转 (IoC) 和依赖注入 (DI) 的功能。也许对你来说也值得一看。

希望这能帮到你!

编辑以考虑评论。


问题在于子viewModel对应的是一个用户控件,而不是像MainWindoViewModel一样的窗口。_dialogService.ShowDialog<WhateverDialog>(this, vmForDialog) .. 当dialogService尝试查找数据上下文为“this”的窗口时,它会失败。请查看DialogService实现中的FindOwnerWindow方法。 - Anirban Paul
我希望我已经解决了这个问题。在我的应用程序中,任何用户控件的相应视图模型都与用户控件关联为用户控件的内容,而不是数据上下文。因此,我需要修改FindOwnerWindow方法。请查看我编辑过的帖子。 - Anirban Paul

0

看看你是否喜欢这个想法... 我正在使用Castle Windsor和Prism,所以你的结果可能会有所不同,但是使用另一个MVVM和IoC的概念应该是相同的。

你可以从MainViewModel.cs开始,它想要打开一个模态对话框

var view = ServiceLocator.Current.GetInstance<SomeDialogView>();
view.ShowDialog();

但是它显然没有遵循你在MainView.xaml中设置的内容

WindowStartupLocation="CenterOwner"

糟糕!

但等等,ServiceLocator岂不是可以直接给我MainView吗?

var view = ServiceLocator.Current.GetInstance<SomeDialogView>();
view.Owner = ServiceLocator.Current.GetInstance<MainView>();  // sure, why not?
view.ShowDialog();

这行代码在我的IoC配置中抛出异常,因为我的IoC注册了视图以具有“瞬态生命周期”。在Castle Windsor中,这意味着每个请求都会提供一个全新的实例,而我需要的是主视图实例本身,而不是一个从未显示过的新实例。

但通过简单地将每个视图的注册从“瞬态”更改

container.Register(Classes.FromAssemblyNamed("WhateverAssemblyTheyreIn")
  .InNamespace("WhateverNamespaceTheyreIn")
  .LifestyleTransient());

通过使用流畅的Unless()和If(),可以稍微更加有选择性。

container.Register(Classes.FromAssemblyNamed("WhateverAssemblyTheyreIn")
  .InNamespace("WhateverNamespaceTheyreIn")
  .Unless(type => type == typeof(MainView))
  .LifestyleTransient());  // all as before but MainView.

container.Register(Classes.FromAssemblyNamed("WhateverAssemblyTheyreIn")
  .InNamespace("WhateverNamespaceTheyreIn")
  .If(type => type == typeof(MainView))
  .LifestyleSingleton());  // set MainView to singleton!

提供的 MainView 是我们想要的!

希望对你有所帮助。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接