XAML 对话框控件:在 WPF 中启用 MVVM 和对话框






4.70/5 (16投票s)
使用单行 XAML 将数据模板化对话框添加到您的应用程序。

引言
如果能像使用其他控件一样在 XAML 中使用对话框,那是不是很棒?也就是说,插入一个 <Dialog>
元素,然后弹出一个模态对话框窗口?这将允许 Model-View-ViewModel (MVVM) 程序在不触及 XAML 的代码隐藏的情况下显示对话框;这在 Windows Presentation Foundation (WPF) 中目前是不可能实现的。本文介绍了 Dialog 控件,该控件既简化了对话框,又使其直接与 MVVM 一起工作。
背景
MVVM 的真正拥护者认为,MVVM 应用程序的 XAML 文件不应该有实质性的代码隐藏。这种限制可以确保 ViewModel 逻辑不会意外地渗透到 View 中。不幸的是,开箱即用地,无法在不编写一些代码隐藏的情况下实现对话框。即使有代码隐藏,对话框也无法访问 WPF 的自动数据模板功能,因为对话框被视为一个单独的逻辑树。由于这些限制,大多数 MVVM 应用程序根本不使用对话框。此控件解决了这些问题中的许多。
Using the Code
对话框控件定义在示例代码的“DialogControl”文件夹中。它都在 MVVMDialogSample.DialogControl
命名空间下。要在 XAML 文件中使用它,首先将命名空间添加到 XAML 的父元素
xmlns:dialog="clr-namespace:MVVMDialogSample.DialogControl"
然后将 Dialog
添加为子控件。对话框控件实际上不会出现在 UI 中,所以只需将其放置在可以正确继承任何所需数据模板的位置即可。一个简单的对话框元素如下所示
<dialog:Dialog Content="Hello World!" Showing="True" />
将控件添加到 XAML 后,可能会出现一个窗口。这是因为 Visual Studio XAML 预览器正在“预览”您的对话框。在使用 Visual Studio 之前必须关闭它,因为它是一个模态对话框。您可以通过设置 Showing="False"
来隐藏对话框。或者,Showing
属性默认为 false
。
运行代码,将出现一个窗口,内容为“Hello World!
”。
更有趣的是将 Content
属性设置为一个复杂对象或绑定到它。对话框将拾取主窗口中的任何适用的数据模板并将其应用于对话框窗口中显示的内容。
在 MVVM 中,Dialog
最常见的用法是将 ViewModel 的属性绑定到 Dialog
的 Content
属性。例如
<dialog:Dialog Content="{Binding Path=DialogViewModel}" />
然后,在 Dialog
上设置一个触发器,该触发器仅在 Content
存在时显示对话框。要显示对话框窗口,只需将绑定的 ViewModel 属性设置为您想在对话框中显示的内容即可。要隐藏它,只需将绑定的属性设置为 null
。这是一个仅在有内容时显示对话框的样式
<Style TargetType="{x:Type dialog:Dialog}">
<Style.Triggers>
<Trigger Property="HasContent" Value="True">
<Setter Property="Showing" Value="True" />
</Trigger>
</Style.Triggers>
</Style>
此外,大多数 MVVM 应用程序会将 ViewModel
的 ICommand
绑定到 CancelCommand
属性。如果用户通过单击 X 关闭对话框,Dialog
将调用存储在 CancelCommand
属性中的命令。这允许 ViewModel 在用户强制取消时进行清理。
因为对话框控件继承自 ContentControl
,它有一些额外的属性,但这些属性没有任何作用。事实上,唯一有作用的属性如下
Content
- 请参阅ContentControl
的文档。ContentStringFormat
- 请参阅ContentControl
的文档。ContentTemplate
- 请参阅ContentControl
的文档。不幸的是,WPF 有一个 bug,将此属性设置在包含ContentPresenter
的模板的 Window 上会引发异常,使得此属性几乎无用。因此,要格式化内容,您必须依赖ContentTemplateSelector
或DataTemplate
。ContentTemplateSelector
- 请参阅ContentControl
的文档。如果此属性和ContentTemplate
属性未指定,对话框控件将尝试查找适当的数据模板。DataContext
- 请参阅ContentControl
的文档。Title
- 对话框的标题。Showing
- 对话框是否显示。CancelCommand
- 在对话框被取消时调用的ICommand
。WindowTemplate
- 窗口的ControlTemplate
。必须有一个派生于或类型为Window
的根元素。默认的Window
在“Themes\Generic.xaml”中指定。
关注点
此控件主要通过将 Dialog
控件的属性绑定到 Window
的实例来工作,以便 Window
保持同步。但是,将 DataTemplate
从对话框控件获取到对话框窗口需要更多工作。对话框窗口有一个特殊的 DataTemplateSelector
,它会询问对话框控件使用哪个 DataTemplate
。由于对话框控件位于主窗口的逻辑树中,因此它会选择正确的 DataTemplate
。这样,对话框窗口就可以获得 DataTemplate
,就像它是主窗口逻辑树中的一个控件一样。如果 Window
类可以作为另一个树的逻辑子项添加,这一切都会变得容易得多,但代码明确禁止这样做。
当 Showing
属性更改时,该控件还会延迟评估对话框的可见性。这是为了防止对话框的 ShowDialog()
和 Hide()
被反复调用,如果 Showing
属性由于强制转换或样式而更改了多次。它通过 Application.Current.Dispatcher
将委托挂钩到 Windows 消息泵来延迟评估。委托会评估一次可见性。虽然这并非绝对必要,但它减少了在复杂情况下 Hide()
和 ShowDialog()
的调用次数。
历史
- 2009/04/25
- 使控件“无外观”(lookless)
- 每次
ShowDialog()
和Close()
时都会销毁并重新创建Window
,这使得IsCancel
始终有效 - 将
Window
的规范移至“Generic.xaml”并添加了WindowTemplate
属性 - 将
ContentStringFormat
、ContentTemplate
、ContentTemplateSelector
连接到Window
- 2009/04/15
- 首次发布