对话框概述 (WPF .NET)

Windows Presentation Foundation (WPF) 为你提供了自行设计对话框的方法。 对话框是窗口,但具有特定的意图和用户体验。 本文讨论对话框的工作原理以及可以创建和使用的对话框类型。 对话框用于:

  • 向用户显示特定信息。
  • 从用户处收集信息。
  • 同时显示并收集信息。
  • 显示操作系统提示,例如打印窗口。
  • 选择文件或文件夹。

这些类型的窗口称为对话框。 对话框可以通过两种方式显示:模式和非模式。

向用户显示模式对话框是一种技术,应用程序使用该技术中断其正在执行的操作,直到用户关闭对话框。 这通常以提示或警报的形式出现。 在关闭对话框之前,无法与应用程序中的其他窗口进行交互。 模式对话框关闭后,应用程序将继续运行。 最常见的对话框用于显示打开文件或保存文件提示、显示打印机对话框或向用户发送一些状态消息。

非模式对话框打开时不会阻止用户激活其他窗口。 例如,如果用户想要在文档中查找特定单词的匹配项,主窗口通常会打开一个对话框,询问用户要查找什么单词。 由于应用程序不想阻止用户编辑文档,因此该对话框不必为模式对话框。 非模式对话框至少提供“关闭”按钮来关闭对话框。 可能还会提供其他按钮来运行特定功能,例如提供“查找下一个”按钮以在单词搜索中查找下一个单词。

你可以使用 WPF 创建多种类型的对话框,例如消息框、通用对话框和自定义对话框。 本文将讨论每种对话框,对话框示例提供了匹配示例。

消息框

消息框是可以用来显示文本信息并允许用户使用按钮做出决定的对话框。 下图显示了一个消息框,框中询问问题并为用户提供三个按钮来回答问题。

Word processor dialog box asking if you want to save the changes to the document before the application closes.

若要创建消息框,请使用 MessageBox 类。 MessageBox 允许你配置消息框文本、标题、图标和按钮。

有关详细信息,请参阅如何打开消息框

通用对话框

Windows 实现了所有应用程序通用的不同类型的可重用对话框,其中包括用于选择文件和打印的对话框。

由于这些对话框是由操作系统提供的,因此它们在操作系统上运行的所有应用程序之间共享。 这些对话框提供一致的用户体验,被称为通用对话框。 当用户在一个应用程序中使用通用对话框时,他们不需要学习如何在其他应用程序中使用该对话框。

WPF 封装了“打开文件”、“保存文件”和“打印”通用对话框,并将它们公开为托管类,供你在独立应用程序中使用。

Open file dialog box called from WPF.

若要详细了解通用对话框,请参阅以下文章:

自定义对话框

虽然通用对话框很有用,并且应尽可能使用,但它们不支持域特定对话框的要求。 在这些情况下,就需要创建自己的对话框。 如我们所见,对话框是具有特殊行为的窗口。 Window 实现了这些行为,你可以使用窗口来创建自定义模式和非模式对话框。

自行创建对话框时,需要考虑许多设计注意事项。 尽管应用程序窗口和对话框有相似之处,例如共享相同的基类,但对话框用于特定目的。 当你需要提示用户提供某种信息或响应时,通常需要对话框。 通常,应用程序会在显示对话框(模式)时暂停,从而限制对应用程序其余部分的访问。 对话框关闭后,应用程序将继续运行。 但是,将交互仅限制于对话框并非必要。

当 WPF 窗口关闭时,它无法重新打开。 自定义对话框是 WPF 窗口,适用相同的规则。 若要了解如何关闭窗口,请参阅如何关闭窗口或对话框

实现对话框

设计对话框时,请遵循以下建议来创造良好的用户体验:

❌ 不要让对话框窗口变得杂乱无章。 对话框体验是让用户输入一些数据或做出选择。

✔️ 务必提供“确定”按钮来关闭窗口。

✔️ 务必将“确定”按钮的 IsDefault 属性设置为 true,以允许用户按 ENTER 键接受并关闭窗口。

✔️ 考虑添加“取消”按钮,以便用户可以关闭窗口并表明他们不想继续操作。

✔️ 务必将“取消”按钮的 IsCancel 属性设置为 true,以允许用户按 ESC 键关闭窗口。

✔️ 务必设置窗口标题,以准确描述对话框所代表的内容,或者用户应对对话框执行的操作。

✔️ 务必为窗口设置最小宽度和高度值,以防止用户将窗口调整得太小。

✔️ 如果 ShowInTaskbar 设置为 false,请考虑禁用调整窗口大小的功能。 可以通过将 ResizeMode 设置为 NoResize 来禁用调整大小

以下代码演示了这种配置。

<Window x:Class="Dialogs.Margins"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Change Margins"
        Closing="Window_Closing"
        MinHeight="200"
        MinWidth="300"
        SizeToContent="WidthAndHeight"
        ResizeMode="NoResize"
        ShowInTaskbar="False"
        WindowStartupLocation="CenterOwner" 
        FocusManager.FocusedElement="{Binding ElementName=leftMarginTextBox}">
    <Grid Margin="10">
        <Grid.Resources>
            <!-- Default settings for controls -->
            <Style TargetType="{x:Type Label}">
                <Setter Property="Margin" Value="0,3,5,5" />
                <Setter Property="Padding" Value="0,0,0,5" />
            </Style>
            <Style TargetType="{x:Type TextBox}">
                <Setter Property="Margin" Value="0,0,0,5" />
            </Style>
            <Style TargetType="{x:Type Button}">
                <Setter Property="Width" Value="70" />
                <Setter Property="Height" Value="25" />
                <Setter Property="Margin" Value="5,0,0,0" />
            </Style>
        </Grid.Resources>

        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto" />
            <ColumnDefinition />
        </Grid.ColumnDefinitions>

        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition />
        </Grid.RowDefinitions>

        <!-- Left,Top,Right,Bottom margins-->
        <Label Grid.Column="0" Grid.Row="0">Left Margin:</Label>
        <TextBox Name="leftMarginTextBox" Grid.Column="1" Grid.Row="0" />

        <Label Grid.Column="0" Grid.Row="1">Top Margin:</Label>
        <TextBox Name="topMarginTextBox" Grid.Column="1" Grid.Row="1"/>

        <Label Grid.Column="0" Grid.Row="2">Right Margin:</Label>
        <TextBox Name="rightMarginTextBox" Grid.Column="1" Grid.Row="2" />

        <Label Grid.Column="0" Grid.Row="3">Bottom Margin:</Label>
        <TextBox Name="bottomMarginTextBox" Grid.Column="1" Grid.Row="3" />

        <!-- Accept or Cancel -->
        <StackPanel Grid.Column="0" Grid.ColumnSpan="2" Grid.Row="4" Orientation="Horizontal" HorizontalAlignment="Right">
            <Button Name="okButton" Click="okButton_Click" IsDefault="True">OK</Button>
            <Button Name="cancelButton" IsCancel="True">Cancel</Button>
        </StackPanel>
    </Grid >
</Window>

上述 XAML 创建了一个类似于下图的窗口:

A dialog box window for WPF that shows left, top, right, bottom text boxes.

打开对话框的 UI 元素

对话框用户体验还扩展到打开对话框的窗口菜单栏或按钮。 当菜单项或按钮运行需要用户通过对话框交互才能继续运行的函数时,控件应在其标题文本的末尾使用省略号:

<MenuItem Header="_Margins..." Click="formatMarginsMenuItem_Click" />
<!-- or -->
<Button Content="_Margins..." Click="formatMarginsButton_Click" />

当菜单项或按钮运行的函数显示无需用户交互的对话框(如“关于”对话框)时,则不需要省略号。

菜单项是向用户提供按相关主题分组的应用程序操作的常用方式。 你可能在许多不同的应用程序上看到过“文件”菜单。 在典型应用程序中,“文件”菜单项提供保存文件、加载文件和打印文件的方法。 如果操作要显示模式窗口,则标题通常包含省略号,如下图所示:

A WPF window that shows menu items with an ellipsis to indicate which item shows a dialog box.

其中两个菜单项带有省略号:...。 这有助于用户确定,当他们选择这些菜单项时,系统会显示一个模式窗口,并暂停应用程序直到用户关闭该窗口。

这种设计技术是向用户传达预期内容的一种简单方法。

按钮

你可以遵循菜单项部分中所述的相同原则。 在按钮文本上使用省略号表示当用户按下按钮时,会显示一个模式对话框。 下图中有两个按钮,哪个按钮会显示对话框一目了然:

A WPF window that shows buttons with an ellipsis to indicate which item shows a dialog box.

返回结果

打开另一个窗口(尤其是模式对话框)是将状态和信息返回给调用代码的好方法。

通过调用 ShowDialog() 显示对话框时,打开对话框的代码会等待 ShowDialog 方法返回结果。 该方法返回结果时,调用它的代码需要决定是继续处理还是停止处理。 用户通常通过按对话框上的“确定”或“取消”按钮做出指示。

按下“确定”按钮时,ShowDialog 应设计为返回 true,而按下“取消”按钮时,应返回 false。 这是通过在按下按钮时设置 DialogResult 属性来实现的。

private void okButton_Click(object sender, RoutedEventArgs e) =>
    DialogResult = true;

private void cancelButton_Click(object sender, RoutedEventArgs e) =>
    DialogResult = false;
Private Sub okButton_Click(sender As Object, e As RoutedEventArgs)
    DialogResult = True
End Sub

Private Sub cancelButton_Click(sender As Object, e As RoutedEventArgs)
    DialogResult = False
End Sub

只有使用 ShowDialog() 显示对话框时,才能设置 DialogResult 属性。 设置 DialogResult 属性后,对话框关闭。

如果按钮的 IsCancel 属性设置为 true,并且使用 ShowDialog() 打开窗口,则 ESC 键将关闭窗口并将 DialogResult 设置为 false

有关关闭对话框的详细信息,请参阅如何关闭窗口或对话框

处理响应

ShowDialog() 返回一个布尔值,指示用户是接受还是取消了对话框。 如果你要提醒用户注意某事,但不要求他们做出决定或提供数据,则可以忽略响应。 也可以通过检查 DialogResult 属性来检查响应。 以下代码演示如何处理响应:

var dialog = new Margins();

// Display the dialog box and read the response
bool? result = dialog.ShowDialog();

if (result == true)
{
    // User accepted the dialog box
    MessageBox.Show("Your request will be processed.");
}
else
{
    // User cancelled the dialog box
    MessageBox.Show("Sorry it didn't work out, we'll try again later.");
}
Dim marginsWindow As New Margins

Dim result As Boolean? = marginsWindow.ShowDialog()

If result = True Then
    ' User accepted the dialog box
    MessageBox.Show("Your request will be processed.")
Else
    ' User cancelled the dialog box
    MessageBox.Show("Sorry it didn't work out, we'll try again later.")
End If

marginsWindow.Show()

非模式对话框

若要显示非模式对话框,请调用 Show()。 该对话框至少应提供“关闭”按钮。 可以提供其他按钮和交互元素来运行特定功能,例如提供“查找下一个”按钮以在单词搜索中查找下一个单词。

由于非模式对话框不会阻止调用代码继续执行,因此你必须提供其他返回结果的方式。 可以执行以下操作之一:

  • 在窗口上公开数据对象属性。
  • 处理调用代码中的 Window.Closed 事件。
  • 在窗口上创建事件,这些事件在用户选择对象或按下特定按钮时引发。

以下示例使用 Window.Closed 事件在对话框关闭时向用户显示消息框。 显示的消息引用已关闭对话框的属性。 有关关闭对话框的详细信息,请参阅如何关闭窗口或对话框

var marginsWindow = new Margins();

marginsWindow.Closed += (sender, eventArgs) =>
{
    MessageBox.Show($"You closed the margins window! It had the title of {marginsWindow.Title}");
};

marginsWindow.Show();
Dim marginsWindow As New Margins

AddHandler marginsWindow.Closed, Sub(sender As Object, e As EventArgs)
                                     MessageBox.Show($"You closed the margins window! It had the title of {marginsWindow.Title}")
                                 End Sub

marginsWindow.Show()

另请参阅