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

时间:2013-03-05 10:23:47

标签: wpf mvvm modal-dialog

我正在开发一个遵循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,我需要在AddExecute方法时显示模态对话框 调用与AddCommand相对应的。 我怎么能做到这一点?

已编辑的代码

     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;
        }

2 个答案:

答案 0 :(得分:4)

好的,如果我说得对,你想打开模态对话框,而不是从MainWindowViewModel打开,而是从另一个ChildViewModel打开?

查看已链接的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)。也许值得为你看看它。

希望这有帮助!

已编辑以考虑评论。

答案 1 :(得分: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注册视图有一个&#34;瞬态生命周期&#34;。在Castle Windsor,这意味着每个请求都是一个全新的实例,我需要 MainView实例本身,而不是一个尚未显示的新实例。

但是,只需更改每个视图的注册状态&#34; transient&#34;

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

通过使用流畅的除非()和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 我们想要的那个!

HTH