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 包含另一个 ViewModel,例如 ChildVM,它与 ChildView 绑定。ChildVM 包含 AddCommand,我需要在调用 AddCommand 对应的 AddExecute 方法时显示 Modal Dialog。我怎样才能做到这一点?

编辑代码

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

2 回答 2

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

为了找到您的视图的所有者窗口,它不是视图本身,您需要修改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;
    }

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

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

据我所知,这是可行的。

附加信息:

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

希望这可以帮助!

编辑考虑评论。

于 2013-03-05T14:23:21.443 回答
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 中,这意味着每个请求都提供了一个全新的实例,我需要MainView 实例本身,而不是尚未显示的新实例

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

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

通过使用流利的除非()和如果()稍微区分

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我们想要的!

高温高压

于 2015-03-22T03:03:20.027 回答