65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.70/5 (16投票s)

2009年4月17日

CPOL

5分钟阅读

viewsIcon

112798

downloadIcon

5320

使用单行 XAML 将数据模板化对话框添加到您的应用程序。

MVVMDialogSample

引言

如果能像使用其他控件一样在 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 的属性绑定到 DialogContent 属性。例如

<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 应用程序会将 ViewModelICommand 绑定到 CancelCommand 属性。如果用户通过单击 X 关闭对话框,Dialog 将调用存储在 CancelCommand 属性中的命令。这允许 ViewModel 在用户强制取消时进行清理。

因为对话框控件继承自 ContentControl,它有一些额外的属性,但这些属性没有任何作用。事实上,唯一有作用的属性如下

  • Content - 请参阅 ContentControl 的文档。
  • ContentStringFormat - 请参阅 ContentControl 的文档。
  • ContentTemplate - 请参阅 ContentControl 的文档。不幸的是,WPF 有一个 bug,将此属性设置在包含 ContentPresenter 的模板的 Window 上会引发异常,使得此属性几乎无用。因此,要格式化内容,您必须依赖 ContentTemplateSelectorDataTemplate
  • 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 属性
    • ContentStringFormatContentTemplateContentTemplateSelector 连接到 Window
  • 2009/04/15
    • 首次发布
© . All rights reserved.