我正在开发一个遵循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;
}
答案 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