对话框概述
独立应用程序通常有一个主窗口,该窗口中不但显示应用程序对其进行操作的主数据,而且还公开通过用户界面 (UI) 机制(例如菜单栏、工具栏、状态栏)处理该数据的功能。不常用的应用程序还可能显示其他窗口来执行以下操作:
-
向用户显示特定信息。
-
收集用户的信息。
-
既显示信息又收集信息。
这些类型的窗口称作“对话框”,对话框有两种类型:模式和无模式。
在某个功能需要用户的其他数据才能继续时,该功能将显示“模式”对话框。因为该功能依赖模式对话框来收集数据,所以模式对话框还可以在其保持为打开状态时防止用户激活应用程序中的其他窗口。在大多数情况下,模式对话框都允许用户在完成模式对话框时通过按“确定”或“取消”按钮给出指示。按“确定”按钮表示用户已输入数据,希望该功能继续处理该数据。按“取消”按钮表示用户希望完全停止执行该功能。模式对话框的最常见示例一般用来演示打开、保存和打印数据。
“无模式”对话框不会在其处于打开状态时阻止用户激活其他窗口。例如,如果用户希望查明某一特殊的单词在文档中出现的次数,则主窗口经常会打开对话框来询问用户在查找什么单词。但是,由于查找某个单词不会妨碍用户编辑文档,因而该对话框不必是模式对话框。无模式对话框中至少有一个“关闭”按钮,用于关闭该对话框,此外,还可能有其他按钮来执行特定的功能,如“查找下一个”按钮,用于查找与单词搜索的查找条件匹配的下一个单词。
Windows Presentation Foundation (WPF) 允许创建多种类型的对话框,包括消息框、通用对话框以及自定义对话框。本主题将逐一讨论这些类型的对话框,Dialog Box Sample(对话框示例)提供了相应的示例。
本主题包括下列各节。
“消息框”是一种可用于显示文字信息的对话框,允许用户通过按钮做出决定。下图演示一个消息框,该消息框显示文字信息,提出问题并为用户提供了三个按钮来回答问题。
![“字处理器”对话框 “字处理器”对话框](http://i.msdn.microsoft.com/dynimg/IC55882.png)
若要创建消息框,请使用 MessageBox 类。通过 MessageBox,可以使用类似于以下内容的代码配置消息框文本、标题、图标和按钮。
若要显示消息框,需要调用 static Show 方法,如下面的代码所示。
当显示消息框的代码需要检测和处理用户决定(按下了哪个按钮)时,该代码可以检查消息框的结果,如下面的代码所示。
// Display message box MessageBoxResult result = MessageBox.Show(messageBoxText, caption, button, icon); // Process message box results switch (result) { case MessageBoxResult.Yes: // User pressed Yes button // ... break; case MessageBoxResult.No: // User pressed No button // ... break; case MessageBoxResult.Cancel: // User pressed Cancel button // ... break; }
有关使用消息框的更多信息,请参见 MessageBox、MessageBox Sample(MessageBox 示例)和 Dialog Box Sample(对话框示例)。
虽然 MessageBox 只是提供简单的对话框用户体验,但是使用 MessageBox 的优势在于,这是在部分信任的安全沙盒(请参见 安全性 (WPF))中运行的应用程序(如 XAML 浏览器应用程序 (XBAP))唯一可以显示的窗口类型。
大多数对话框都显示和收集比消息框的结果更复杂的数据,包括文本、选择(复选框)、互斥选择(单选按钮)以及列表选择(列表框、组合框、下拉列表框)。对于这些数据,Windows Presentation Foundation (WPF) 提供了几个通用对话框,并允许您创建自己的对话框,不过这两类对话框的使用都仅限于在完全信任环境下运行的应用程序。
Windows 实现各种对所有应用程序都通用的可重用对话框,包括用于打开文件、保存文件和打印的对话框。由于这些对话框是操作系统实现的,因此,可以在操作系统上运行的所有应用程序中进行共享,这样有助于实现用户体验的一致性;用户在某一应用程序中熟悉操作系统提供的对话框的使用后,就无需学习如何在其他应用程序中使用该对话框。因为这些对话框对所有应用程序均可用,而且有助于提供一致的用户体验,所以它们称作“通用对话框”。
Windows Presentation Foundation (WPF) 封装用于打开文件、保存文件和打印的通用对话框,并将它们公开为托管类以供您在独立应用程序中使用。本主题简要概述每个对话框:
“打开文件”对话框
下图演示“打开文件”对话框,文件打开功能使用该对话框检索要打开的文件的名称。
![“打开”对话框 “打开”对话框](http://i.msdn.microsoft.com/dynimg/IC525031.png)
常见的“打开文件”对话框实现为 OpenFileDialog 类,位于 Microsoft.Win32 命名空间中。下面的代码演示如何创建、配置和显示“保存文件”对话框以及如何处理结果。
// Configure open file dialog box Microsoft.Win32.OpenFileDialog dlg = new Microsoft.Win32.OpenFileDialog(); dlg.FileName = "Document"; // Default file name dlg.DefaultExt = ".txt"; // Default file extension dlg.Filter = "Text documents (.txt)|*.txt"; // Filter files by extension // Show open file dialog box Nullable<bool> result = dlg.ShowDialog(); // Process open file dialog box results if (result == true) { // Open document string filename = dlg.FileName; }
有关“打开文件”对话框的更多信息,请参见 Microsoft.Win32.OpenFileDialog。
![]() |
---|
在部分信任环境下运行的应用程序可以使用 OpenFileDialog 安全地检索文件名(请参见 安全性 (WPF))。 |
“保存文件”对话框
下图演示“保存文件”对话框,文件保存功能使用该对话框检索要保存的文件的名称。
![“另存为”对话框 “另存为”对话框](http://i.msdn.microsoft.com/dynimg/IC525032.png)
常见的“保存文件”对话框实现为 SaveFileDialog 类,位于 Microsoft.Win32 命名空间中。下面的代码演示如何创建、配置和显示“保存文件”对话框以及如何处理结果。
// Configure save file dialog box Microsoft.Win32.SaveFileDialog dlg = new Microsoft.Win32.SaveFileDialog(); dlg.FileName = "Document"; // Default file name dlg.DefaultExt = ".text"; // Default file extension dlg.Filter = "Text documents (.txt)|*.txt"; // Filter files by extension // Show save file dialog box Nullable<bool> result = dlg.ShowDialog(); // Process save file dialog box results if (result == true) { // Save document string filename = dlg.FileName; }
有关“保存文件”对话框的更多信息,请参见 Microsoft.Win32.SaveFileDialog。
“打印”对话框
下图演示“打印”对话框,打印功能使用该对话框选择和配置用户用来打印数据的打印机。
![Print dialog box Print dialog box](http://i.msdn.microsoft.com/dynimg/IC171219.png)
常见的“打印”对话框实现为 PrintDialog 类,位于 System.Windows.Controls 命名空间中。下面的代码演示如何创建、配置和显示该对话框。
// Configure printer dialog box System.Windows.Controls.PrintDialog dlg = new System.Windows.Controls.PrintDialog(); dlg.PageRangeSelection = PageRangeSelection.AllPages; dlg.UserPageRangeEnabled = true; // Show save file dialog box Nullable<bool> result = dlg.ShowDialog(); // Process save file dialog box results if (result == true) { // Print document }
有关“打印”对话框的更多信息,请参见 System.Windows.Controls.PrintDialog。有关 WPF 中的打印功能的详细讨论,请参见打印概述。
虽然通用对话框很有用,而且在可能的情况下也会使用它们,但它们不支持特定于域的对话框的要求。在这些情况下,您需要创建自己的对话框。正如我们将要了解的,对话框是一种具有特殊行为的窗口。这些行为是 Window 实现的,因此,您需要使用 Window 来创建自定义的模式对话框和无模式对话框。
创建模式自定义对话框
本主题演示如何使用 Window 创建典型的模式对话框实现,其中使用 Margins 对话框作为示例(请参见 Dialog Box Sample(对话框示例))。下图演示 Margins 对话框。
![Margins dialog box Margins dialog box](http://i.msdn.microsoft.com/dynimg/IC52249.png)
配置模式对话框
典型对话框的用户界面包括:
-
收集所需的数据时需要使用的各种控件。
-
显示一个“确定”按钮,用户可以单击该按钮关闭对话框,返回到该功能并继续处理。
-
显示一个“取消”按钮,用户可以单击该按钮关闭对话框并停止该功能的进一步处理。
-
在标题栏中显示一个“关闭”按钮。
-
显示一个图标。
-
显示“最小化”、“最大化”和“还原”按钮。
-
显示一个“系统”菜单,可用于最小化、最大化、还原和关闭对话框。
-
在打开该对话框的窗口上方的中心位置打开。
-
对话框应尽可能可调整大小,以防止对话框过小;若要为用户提供有效的默认大小,您需要分别设置默认尺寸和最小尺寸。
-
应将按 Esc 键配置为与按“取消”按钮等效的键盘快捷键。可以通过将“取消”按钮的 IsCancel 属性设置为 true 来实现此目的。
-
应将按 Enter(或 Return)键配置为与按“确定”按钮等效的键盘快捷键。可以通过将“确定”按钮的 IsDefault 属性设置为 true 来实现此目的。
下面的代码演示此配置。
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Class="SDKSample.MarginsDialogBox" xmlns:local="clr-namespace:SDKSample" Title="Margins" Height="190" Width="300" MinHeight="10" MinWidth="300" ResizeMode="CanResizeWithGrip" ShowInTaskbar="False" WindowStartupLocation="CenterOwner" FocusManager.FocusedElement="{Binding ElementName=leftMarginTextBox}"> <Grid> ... <!-- Accept or Cancel --> <StackPanel Grid.Column="0" Grid.ColumnSpan="2" Grid.Row="4"> <Button Name="okButton" Click="okButton_Click" IsDefault="True">OK</Button> <Button Name="cancelButton" IsCancel="True">Cancel</Button> </StackPanel> </Grid > </Window>
Imports System.Windows ' Window, RoutedEventArgs, IInputElement, DependencyObject Imports System.Windows.Controls ' Validation Imports System.Windows.Input ' Keyboard Namespace SDKSample Public Class MarginsDialogBox Inherits Window Public Sub New() Me.InitializeComponent() End Sub ... End Class End Namespace
using System.Windows; // Window, RoutedEventArgs, IInputElement, DependencyObject using System.Windows.Controls; // Validation using System.Windows.Input; // Keyboard namespace SDKSample { public partial class MarginsDialogBox : Window { public MarginsDialogBox() { InitializeComponent(); } ... } }
用户的对话框体验还将扩展到打开该对话框的窗口的菜单栏。如果菜单项运行的功能要求用户先通过对话框进行交互才能继续,则对应于该功能的菜单项的标题中将有省略号,如此处所示。
<!--Main Window--> ... <MenuItem Name="formatMarginsMenuItem" Header="_Margins..." Click="formatMarginsMenuItem_Click" />
如果菜单项运行的功能显示的对话框(如“关于”对话框)不要求用户交互,则不需要省略号。
打开模式对话框
对话框通常在用户选择某一菜单项执行特定于域的功能(如在字处理器中设置文档的页边距)后显示。以对话框的形式显示窗口与显示正常窗口类似,不过前者要求特定于对话框的额外配置。下面的代码演示实例化、配置和打开对话框的整个过程。
using System; // EventArgs using System.ComponentModel; // CancelEventArgs using System.Windows; // Window, MessageBoxXxx, RoutedEventArgs using System.Windows.Controls; // TextChangedEventArgs using Microsoft.Win32; // OpenFileDialog namespace SDKSample { public partial class MainWindow : Window { ... void formatMarginsMenuItem_Click(object sender, RoutedEventArgs e) { // Instantiate the dialog box MarginsDialogBox dlg = new MarginsDialogBox(); // Configure the dialog box dlg.Owner = this; dlg.DocumentMargin = this.documentTextBox.Margin; // Open the dialog box modally dlg.ShowDialog(); ... } ... } }
此处,该代码将默认信息(当前的页边距)传递给对话框。同时,它还将设置 Window.Owner 属性,该属性引用显示对话框的窗口。通常,应始终将对话框的所有者设置成提供对所有对话框都通用的与窗口状态相关的行为(有关更多信息,请参见 WPF Windows 概述)。
![]() |
---|
您必须提供一个所有者,以支持对话框的用户界面 (UI) 自动化(请参见 UI 自动化概述)。 |
配置该对话框后,将通过调用 ShowDialog 方法以模式方式显示该对话框.
验证用户提供的数据
在打开对话框以及用户提供所需的数据时,对话框负责确保提供的数据有效,原因如下:
-
从安全角度讲,应验证所有输入。
-
从特定于域的角度讲,数据验证可防止该代码处理错误的数据,因为这样可能会引发异常。
-
从用户体验的角度讲,对话框可以通过向用户显示哪些输入数据无效来为用户提供帮助。
-
从性能角度讲,多层应用程序中的数据验证可以减少客户端和应用程序层之间的往返次数,尤其是在该应用程序由 Web 服务或基于服务器的数据库组成时。
若要验证 WPF 中的绑定控件,您需要定义验证规则,然后将其与该绑定关联。验证规则是派生自 ValidationRule 的自定义类。下面的示例演示验证规则 MarginValidationRule,该规则检查绑定值是否是 Double 以及是否位于指定的范围内。
using System.Globalization; using System.Windows.Controls; namespace SDKSample { public class MarginValidationRule : ValidationRule { double minMargin; double maxMargin; public double MinMargin { get { return this.minMargin; } set { this.minMargin = value; } } public double MaxMargin { get { return this.maxMargin; } set { this.maxMargin = value; } } public override ValidationResult Validate(object value, CultureInfo cultureInfo) { double margin; // Is a number? if (!double.TryParse((string)value, out margin)) { return new ValidationResult(false, "Not a number."); } // Is in range? if ((margin < this.minMargin) || (margin > this.maxMargin)) { string msg = string.Format("Margin must be between {0} and {1}.", this.minMargin, this.maxMargin); return new ValidationResult(false, msg); } // Number is valid return new ValidationResult(true, null); } } }
在此代码中,通过重写 Validate 方法来实现验证规则的验证逻辑,该方法对数据进行验证并返回相应的 ValidationResult。
若要将验证规则与绑定控件关联,您需要使用以下标记。
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Class="SDKSample.MarginsDialogBox" xmlns:local="clr-namespace:SDKSample" Title="Margins" Height="190" Width="300" MinHeight="10" MinWidth="300" ResizeMode="CanResizeWithGrip" ShowInTaskbar="False" WindowStartupLocation="CenterOwner" FocusManager.FocusedElement="{Binding ElementName=leftMarginTextBox}"> <Grid> ... <Label Grid.Column="0" Grid.Row="0">Left Margin:</Label> <TextBox Name="leftMarginTextBox" Grid.Column="1" Grid.Row="0"> <TextBox.Text> <Binding Path="Left" UpdateSourceTrigger="PropertyChanged"> <Binding.ValidationRules> <local:MarginValidationRule MinMargin="0" MaxMargin="10" /> </Binding.ValidationRules> </Binding> </TextBox.Text> </TextBox> ... </Window>
对验证规则进行关联之后,WPF 将在数据输入绑定控件时自动应用该规则。如果控件包含无效的数据,则 WPF 将在无效控件周围显示一个红色边框,如下图所示。
![无效左边距 无效左边距](http://i.msdn.microsoft.com/dynimg/IC118829.png)
在用户输入有效数据之前,WPF 不会将用户限制于无效的控件。这对于对话框来说是很有利的;无论数据是否有效,用户都应当能够在对话框中自由导航控件。但是,这意味着用户可以输入无效的数据,然后按“确定”按钮。因此,在按“确定”按钮时,您的代码还需要通过处理 Click 事件来验证对话框中的所有控件。
using System.Windows; // Window, RoutedEventArgs, IInputElement, DependencyObject using System.Windows.Controls; // Validation using System.Windows.Input; // Keyboard namespace SDKSample { public partial class MarginsDialogBox : Window { ... void okButton_Click(object sender, RoutedEventArgs e) { // Don't accept the dialog box if there is invalid data if (!IsValid(this)) return; ... } // Validate all dependency objects in a window bool IsValid(DependencyObject node) { // Check if dependency object was passed if (node != null) { // Check if dependency object is valid. // NOTE: Validation.GetHasError works for controls that have validation rules attached bool isValid = !Validation.GetHasError(node); if (!isValid) { // If the dependency object is invalid, and it can receive the focus, // set the focus if (node is IInputElement) Keyboard.Focus((IInputElement)node); return false; } } // If this dependency object is valid, check all child dependency objects foreach (object subnode in LogicalTreeHelper.GetChildren(node)) { if (subnode is DependencyObject) { // If a child dependency object is invalid, return false immediately, // otherwise keep checking if (IsValid((DependencyObject)subnode) == false) return false; } } // All dependency objects are valid return true; } } }
此代码将在窗口上枚举所有依赖项对象。如果有任何一个对象无效(由 GetHasError 返回),则此无效的控件将获得焦点,IsValid 方法将返回 false 并将该窗口视为无效。
一旦对话框有效,则可以安全地关闭并返回。在返回过程中,需要向调用函数返回一个结果。
设置模式对话框结果
使用 ShowDialog 打开对话框与调用方法基本类似:使用 ShowDialog 打开对话框的代码将一直等待,直到 ShowDialog 返回。当 ShowDialog 返回时,调用它的代码需要基于用户按下的是“确定”按钮还是“取消”按钮来决定是继续处理还是停止处理。为便于做出此决定,该对话框需要返回用户的选择作为从 ShowDialog 方法返回的 Boolean 值。
在单击“确定”按钮时,ShowDialog 应返回 true。可以通过设置在单击“确定”按钮时该对话框的 DialogResult 属性来实现此目的。
请注意,设置 DialogResult 属性还会导致窗口自动关闭,从而减少对显式调用 Close 的需要。
在单击“取消”按钮时,ShowDialog 应返回 false,从而还需要设置 DialogResult 属性。
using System.Windows; // Window, RoutedEventArgs, IInputElement, DependencyObject using System.Windows.Controls; // Validation using System.Windows.Input; // Keyboard namespace SDKSample { public partial class MarginsDialogBox : Window { ... void cancelButton_Click(object sender, RoutedEventArgs e) { // Dialog box canceled this.DialogResult = false; } ... } }
当将按钮的 IsCancel 属性设置为 true 并且用户按下“取消”按钮或 Esc 键时,DialogResult 会自动设置为 false。下面的标记与上面的代码效果相同,但不需要处理 Click 事件。
<Button Name="cancelButton" IsCancel="True">Cancel</Button>
在用户按下标题栏中的“关闭”按钮或从“系统”菜单中选择“关闭”菜单项时,对话框会自动返回 false。
处理从模式对话框返回的数据
通过某个对话框设置 DialogResult 时,打开该对话框的功能可以通过在 ShowDialog 返回时检查 DialogResult 属性来获取该对话框的结果。
using System; // EventArgs using System.ComponentModel; // CancelEventArgs using System.Windows; // Window, MessageBoxXxx, RoutedEventArgs using System.Windows.Controls; // TextChangedEventArgs using Microsoft.Win32; // OpenFileDialog namespace SDKSample { public partial class MainWindow : Window { ... void formatMarginsMenuItem_Click(object sender, RoutedEventArgs e) { ... // Process data entered by user if dialog box is accepted if (dlg.DialogResult == true) { // Update fonts this.documentTextBox.Margin = dlg.DocumentMargin; } } ... } }
如果对话框结果为 true,则该功能将其用作检索和处理用户提供的数据的提示。
![]() |
---|
ShowDialog 返回后,对话框将无法重新打开,而需要创建一个新实例。 |
如果对话框结果为 false,则该功能应相应地结束处理。
创建无模式自定义对话框
无模式对话框(如下图所示的“查找”对话框)的基本外观与模式对话框相同。
![Find dialog box Find dialog box](http://i.msdn.microsoft.com/dynimg/IC109292.png)
但它们的行为略有差异,如下一节所述。
打开无模式对话框
无模式对话框是通过调用 Show 方法打开的。
<!--Main Window-->
Imports System ' EventArgs Imports System.ComponentModel ' CancelEventArgs Imports System.Windows ' Window, MessageBoxXxx, RoutedEventArgs Imports System.Windows.Controls ' TextChangedEventArgs Imports Microsoft.Win32 ' OpenFileDialog Namespace SDKSample Public Class MainWindow Inherits Window ... Private Sub editFindMenuItem_Click(ByVal sender As Object, ByVal e As RoutedEventArgs) Dim dlg As New FindDialogBox(Me.documentTextBox) dlg.Owner = Me AddHandler dlg.TextFound, New TextFoundEventHandler(AddressOf Me.dlg_TextFound) dlg.Show() End Sub ... End Class End Namespace
using System; // EventArgs using System.ComponentModel; // CancelEventArgs using System.Windows; // Window, MessageBoxXxx, RoutedEventArgs using System.Windows.Controls; // TextChangedEventArgs using Microsoft.Win32; // OpenFileDialog namespace SDKSample { public partial class MainWindow : Window { ... void editFindMenuItem_Click(object sender, RoutedEventArgs e) { // Instantiate the dialog box FindDialogBox dlg = new FindDialogBox(this.documentTextBox); // Configure the dialog box dlg.Owner = this; dlg.TextFound += new TextFoundEventHandler(dlg_TextFound); // Open the dialog box modally dlg.Show(); } ... } }
与 ShowDialog 不同的是,Show 会立即返回。因此,调用窗口无法告知何时关闭无模式对话框,因而不知道何时检查对话框结果或从该对话框获取数据进行进一步处理。该对话框而是需要创建另一种方法将数据返回给调用窗口进行处理。
处理从无模式对话框返回的数据
在本例中,根据无任何特定频率下要搜索的文本,FindDialogBox 可能向主窗口返回一个或多个查找结果。与模式对话框一样,无模式对话框使用属性返回结果。但是,拥有对话框的窗口需要知道何时检查这些属性。启用此功能的一种方法是让该对话框实现只要找到文本就会引发的事件。FindDialogBox 会出于此目的而实现 TextFoundEvent,这首先需要委托。
使用 TextFoundEventHandler 委托,FindDialogBox 可以实现 TextFoundEvent。
using System; // EventArgs using System.Windows; // Window, MessageBoxXxx, RoutedEventArgs using System.Windows.Controls; // TextBox, TextChangedEventArgs using System.Text.RegularExpressions; // Regex namespace SDKSample { public partial class FindDialogBox : Window { public event TextFoundEventHandler TextFound; protected virtual void OnTextFound() { TextFoundEventHandler textFound = this.TextFound; if (textFound != null) textFound(this, EventArgs.Empty); } ... } }
因此,Find 可以在找到搜索结果时引发该事件。
using System; // EventArgs using System.Windows; // Window, MessageBoxXxx, RoutedEventArgs using System.Windows.Controls; // TextBox, TextChangedEventArgs using System.Text.RegularExpressions; // Regex namespace SDKSample { public partial class FindDialogBox : Window { ... void findNextButton_Click(object sender, RoutedEventArgs e) { ... // Text found this.index = match.Index; this.length = match.Length; OnTextFound(); ... } ... } }
然后,所有者窗口需要向此事件注册并对其进行处理。
using System; // EventArgs using System.ComponentModel; // CancelEventArgs using System.Windows; // Window, MessageBoxXxx, RoutedEventArgs using System.Windows.Controls; // TextChangedEventArgs using Microsoft.Win32; // OpenFileDialog namespace SDKSample { public partial class MainWindow : Window { ... void dlg_TextFound(object sender, EventArgs e) { // Get the find dialog box that raised the event FindDialogBox dlg = (FindDialogBox)sender; // Get find results and select found text this.documentTextBox.Select(dlg.Index, dlg.Length); this.documentTextBox.Focus(); } } }
关闭无模式对话框
因为无需设置 DialogResult,所以可以使用系统提供机制来关闭无模式对话框,其中包括:
-
单击标题栏中的“关闭”按钮。
-
按 Alt + F4。
-
从“系统”菜单中选择“关闭”。
此外,在单击“关闭”按钮时,您的代码还可以调用 Close。
using System; // EventArgs using System.Windows; // Window, MessageBoxXxx, RoutedEventArgs using System.Windows.Controls; // TextBox, TextChangedEventArgs using System.Text.RegularExpressions; // Regex namespace SDKSample { public partial class FindDialogBox : Window { ... void closeButton_Click(object sender, RoutedEventArgs e) { // Close dialog box this.Close(); } } }