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

介绍 TaskDialogLib:XAML 中的任务对话框

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.53/5 (7投票s)

2014年3月31日

LGPL3

12分钟阅读

viewsIcon

18622

一个免费开源的 XAML 任务对话框库。

下载项目(集成演示和源代码 ZIP 文件)- 104 KB
官方 NuGet 包
官方 GitHub 仓库

引言

什么是任务对话框?

任务对话框是 Windows® Vista 中引入的一种对话框,作为 Microsoft 通用控件库 (ComCtl32.dll) 6.0 的一部分。它是一种高级的消息框,提供了许多额外功能,例如显示自定义按钮、进度条和文本中的超链接。

根据 MSDN 上的参考文档,任务对话框

...是一个可以用来显示信息并接收用户简单输入的对话框。与消息框一样,它会根据您设置的参数由操作系统进行格式化。然而,任务对话框比消息框具有更多的功能。

在应用程序中使用任务对话框有两种官方支持的方式:由于它作为通用控件可用,因此在所有 C++/MFC 原生 Windows 应用程序中都非常容易和直接使用;对于托管语言/.NET,Microsoft 在名为 Windows API Code Pack for Microsoft .NET Framework 的库中提供了一个包装器,该库可在 MSDN 代码库中找到。

TaskDialogLib 是什么?

TaskDialogLib 是我编写的一个轻量级库,旨在克服 Microsoft 托管代码包装器库的一些限制。虽然这个库实际上为在托管代码中使用任务对话框提供了坚实的基础,但它有两个缺点:

  1. 它仅对 Windows Forms 有用,因为它缺少 WPF 的任何设计支持,因此,如果要从 WPF 使用它,需要大量的代码隐藏。
  2. 托管 API 的设计可以更好,因为它(在我看来)过于倾向于模仿原始的原生任务对话框 API。

我非常喜欢任务对话框的外观和感觉,并认为它比简单的消息框更好。由于我希望在未来的 WPF 应用程序项目中广泛使用它,“必须有一种更简单的方法在 WPF 中使用它”,我想。

问题与解决方案

当我开始思考如何简化在 WPF 应用程序中使用任务对话框 API 并使其更符合 Windows Presentation Foundation 的原则时,这一点几乎立刻就变得清晰了:“如果我可以在 XAML 中设计我的所有应用程序、窗口、用户控件、样式等,并将其与代码隐藏分开,那么为任务对话框做同样的事情不也很棒吗?”

用 XAML 设计一个任务对话框不是很好吗,像这样...

<TaskDialog x:Class="TaskDialogLibTest.XamlTaskDialog"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns="http://schemas.flatcode.net/2014/presentation"
            Title="XAML Task Dialog"
            Instruction="Hello, World!"
            DefaultIcon="Information">
    <TaskDialog.Buttons>
      <TaskDialogButton x:Name="ClickMeButton"
                        Title="Click Me!" 
                        Click="ClickMeButton_Click" />
    </TaskDialog.Buttons>
    Welcome to your first TaskDialog designed in XAML.
</TaskDialog>

...并将相应的代码隐藏开销最小化到像这样的简单程度

partial class XamlTaskDialog : TaskDialog
{
    public XamlTaskDialog()
    {
        InitializeComponent();
    }

    void ClickMeButton_Click(Object sender, EventArgs e)
    {
        // Do some stuff...
    }
}

TaskDialogLib 就是这个问题的答案。它将上面的示例变成了现实。使用 TaskDialogLib,现在可以在 XAML 中设计任务对话框,并使用代码隐藏来编写逻辑。

实现

由于出于许可原因,我不想重用 Windows API Code Pack(顺便说一句,它是开源的),而且仅仅因为我渴望在此过程中学习更多内部工作原理,我决定从头开始,完全独立地实现整个 TaskDialogLib,不涉及任何第三方代码。结果是一个小型轻量级的对象模型,旨在与 XAML 和 WPF 配合使用效果最佳。您仍然可以将其用于 Windows Forms 甚至控制台应用程序,但不如 WPF 那样优雅,并且需要编写更多代码才能使其正常工作。

TaskDialogLib 的核心是 TaskDialog 类。此类及其所有元素(按钮、单选按钮、链接和进度条)都派生自 DependencyObject 类,以便在所有重要属性上支持数据绑定。

背景

要使用 TaskDialogLib,您只需要对 XAML 语言以及如何为 Windows Presentation Foundation 设计和编写应用程序有基本的了解。TaskDialogLib 的一个巨大优点是它向开发者隐藏了底层原生 API 的所有复杂细节,并使设计和编码像您上面看到的示例一样简单直观。

然而,要完全理解任务对话框及其所有功能,您可以随时查阅 MSDN 上关于任务对话框的参考文档。CodeProject 上也有一个很好的文章 这里,介绍了如何使用前面提到的 Windows API Code Pack 在 Windows Forms 中使用任务对话框,这可能有助于更好地理解此代码。

使用代码

您可以通过两种方式在 WPF 应用程序中使用 TaskDialogLib 来使用任务对话框:

  1. 您可以像编写任何其他 Window 或 User Control 一样在 XAML 中编写您的任务对话框,在代码隐藏文件中实现逻辑,并通过创建其实例并根据情况调用其 Show()ShowModal() 方法来显示它。
  2. 对于外观和感觉更像传统消息框的简单任务对话框,您无需编写任何 XAML 和代码隐藏。TaskDialog 类提供了 Show()ShowModal() 方法的静态对应项,这些方法类似于 WPF MessageBox 的行为。

虽然第二种方法非常直接,但接下来的部分将仅更详细地描述第一种方法。

必备组件

首先,在使用任何这些功能之前,您必须确保在应用程序清单中声明了对 Microsoft Common Controls 库 6.0 的依赖。否则,任何在运行时显示任务对话框的尝试都将失败并抛出 NotSupportedException

要使用 Microsoft Common Controls 库 6.0,只需将以下部分添加到您的应用程序清单文件中即可

<dependency>
    <dependentAssembly>
        <assemblyIdentity
            type="win32"
            name="Microsoft.Windows.Common-Controls"
            version="6.0.0.0"
            processorArchitecture="*"
            publicKeyToken="6595b64144ccf1df"
            language="*" />
    </dependentAssembly>
</dependency>

XAML 中的任务对话框

让我们再回顾一下开头的示例

<TaskDialog x:Class="TaskDialogLibTest.XamlTaskDialog"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns="http://schemas.flatcode.net/2014/presentation"
            Title="XAML Task Dialog"
            Instruction="Hello, World!"
            DefaultIcon="Information">
    <TaskDialog.Buttons>
      <TaskDialogButton x:Name="ClickMeButton"
                        Title="Click Me!" 
                        Click="ClickMeButton_Click" />
    </TaskDialog.Buttons>
    Welcome to your first task dialog designed in XAML.
</TaskDialog>

编译并显示后,将显示以下任务对话框

XAML Task Dialog Example 1

TaskDialog 类的所有属性名称都基于您可以在原生 TASKDIALOGCONFIG 结构中定义的字段和标志。您将设置的最常见属性如下(所有这些都是可选的):

  • Title:任务对话框的窗口标题。
  • Instruction:主要说明,可以算作一个标题。
  • Content:任务对话框的主要文本。
  • ExpandedInformation:当任务对话框展开时显示的附加文本(只有当设置了此属性时,展开/折叠才可用)。
  • Footer:显示在页脚中的文本。
  • DefaultIcon:要显示的默认图标。
  • CommonButtons:如果没有定义自定义按钮,则显示的默认按钮。
  • EnableHyperlinks:在 ContentExpandedInformationFooter 部分启用超链接。

使用按钮和单选按钮

TaskDialog 类分别提供了 ButtonsRadioButtons 集合属性来处理按钮和单选按钮。

下面的标记是在 XAML 中向任务对话框添加按钮和两个单选按钮的示例

<TaskDialog.Buttons>
    <TaskDialogButton x:Name="ClickMeButton"
                      Title="Click Me!"
                      Click="ClickMeButton_Click" />
</TaskDialog.Buttons>
<TaskDialog.RadioButtons>
    <TaskDialogRadioButton Title="Option 1" />
    <TaskDialogRadioButton Title="Option 2" />
</TaskDialog.RadioButtons>

请注意,Click 事件直接在按钮上处理。这比原生版本有了可喜的改进,在原生版本中,所有事件都通过通知消息传递到任务对话框回调过程,您需要编写自己的逻辑来处理它。而 TaskDialog 类则透明地将这些通知路由到相应的按钮并引发其点击事件。

使用超链接

任务对话框允许您在某些文本部分(即 ContentExpandedInformationFooter)中放置内嵌超链接。在原生版本中,您通过使用 HTML 风格的语法来做到这一点:

Click <A HREF="http://www.example.com">here</A> for further information.

现在,在 XAML 中使用这种语法会非常麻烦,因为 XAML 基于 XML,如果解析器在标记中找到 HTML 标签,它将抛出异常。一种避免这种情况的方法是为超链接使用 CDATA 部分:

Click <![CDATA[<A HREF="http://www.example.com">here</A>]]> for further information.

虽然这在 TaskDialog 类上确实有效,但这样做有一个问题:我如何知道超链接何时被实际点击?TaskDialog 类本身没有提供任何事件处理程序来通知我超链接何时被点击。那么,XAML 中是如何正确声明超链接的呢?

好吧,这是 XAML 中的做法:

<TaskDialog.Content>
  <TaskDialogText>
    Click <TaskDialogLink x:Name="ExampleLink" Uri="http://www.example.com" Click="ExampleLink_Click">here</TaskDialogLink> for further information.
  </TaskDialogText>
</TaskDialog.Content>

如果您查看 TaskDialogContentExpandedInformationFooter 属性的类型,您会发现它们实际上是 Object 类型。因此,无论您将其“放入”什么内容,“TaskDialog”类都会在其上调用 ToString() 来在任务对话框上显示其内容。

TaskDialogLib 为包含超链接的内嵌文本提供了一个特殊的类:TaskDialogText。这个类基本上包含一个 TaskDialogTextElement 对象的集合,库中有该抽象类型的两个实现:TaskDialogRunTaskDialogLink。虽然在 TaskDialogText 节点标记中的任何字符串都会被自动包装在 TaskDialogRun 中,但您可以为每个超链接声明一个 TaskDialogLink,并在代码隐藏中为其 Click 事件定义一个事件处理程序,就像按钮和单选按钮一样。

请注意,必须将 EnableHyperlinks 属性设置为 true 才能使用超链接;否则,它们将不可点击,仅格式化为普通文本。

使用进度条

任务对话框的强大功能之一是可以在其上放置进度条:

<TaskDialog.ProgressBar>
    <TaskDialogProgressBar Minimum="0" Maximum="80" Value="20" />
</TaskDialog.ProgressBar>

XAML 版本真正出色的一点是,所有 MinimumMaximumValue 属性都是依赖项属性,因此可以用于数据绑定场景,其中属性更改会立即反映在任务对话框上,而无需额外一行代码。要在原生版本中做到这一点,您可能需要在任务对话框上使用计时器并自己处理所有更新。

使用自定义图标

可以为任务对话框或页脚定义自定义图标,TaskDialog 类为此场景提供了 IconFooterIcon 属性。它们也都是依赖项属性,支持数据绑定,并且为了与 WPF 的其余部分完全集成,它们的类型为 ImageSource

请注意,如果想使用自定义图标,必须将 UseDefaultIcon 属性设置为 false。否则,无论您是否指定了图标,都将使用默认图标。

使用计时器

任务对话框的原生 API 提供了一个计时器,启用后,它会大约每 200 毫秒向任务对话框回调过程发送一个计时器通知。TaskDialogLib 的 TaskDialog 类也提供了此功能。

要使用计时器,只需将 EnableTimer 属性设置为 true,并将事件处理程序附加到 Timer 事件。

处理结果

Show()ShowModal() 方法都是阻塞调用,在任务对话框关闭之前不会返回。这些方法的返回类型是 TaskDialogResult。这是一个值类型,包含三个属性:

  • SelectedButton
  • SelectedRadioButton
  • VerificationChecked

SelectedButtonSelectedRadioButton 都是 Int32 值,而后者始终表示 RadioButtons 集合中的索引,SelectedButton 属性的含义取决于选择了哪种类型的按钮。如果选择了由 CommonButtons 属性定义的按钮,则属性值表示 TaskDialogButtons 枚举的常量;否则,它将是 Buttons 集合中的索引。因此,为避免可能重叠的索引造成混淆,您不应同时在任务对话框中使用自定义按钮和通用按钮。

VerificationChecked 属性是 Boolean 类型,用于确定任务对话框关闭时验证复选框是否被选中。如果任务对话框没有此复选框,则此属性无意义,应忽略。

限制

TaskDialog 类不代表从原生到托管代码的一对一转换,并存在以下限制:

  • 不支持导航和页面来模仿向导式 UI。这是故意的,因为我认为它不是真正需要的,而且对于多页面场景,WPF 有比使用任务对话框更好的解决方案。
  • 不支持在对话框单位中指定宽度。使用 TaskDialog 类创建的所有任务对话框将始终由操作系统自动调整大小。但是,SizeToContent 属性是支持的。

注释

我想指出,我不是一个专业的开发者,在我写过的其他几个小型项目之外,这是我开始的第一个真正复杂的项目。请在审阅代码时记住这一点,它的质量肯定不如以开发为生的人所写的,我将非常感谢任何改进的建议和技巧以及 bug 报告。此外,我不是英语母语者,所以请原谅任何语法和拼写错误,我已经尽力写了这篇相当长的文章。

创建这个小型库非常有趣,并且在开发过程中认识到 XAML 实际上是一个非常强大的概念,而不仅仅是您在开始时看到的 WPF,这确实令人开明。

总之,希望您喜欢它,并能在您自己的项目中找到一些用途。

关注点

在我研究期间,我偶然发现了 CodeProject 上的一些其他文章,这些文章以一种或另一种方式处理任务对话框,您可能会觉得它们很有趣:

TaskDialog for WinForms
一个可自定义的 WPF TaskDialog
TDXML:带可视化任务对话框编辑器,基于 XML 的任务对话框

历史

  • 2014 年 3 月 30 日:TaskDialogLib (1.0.0) 和本文的首次发布。
  • 2014 年 4 月 7 日:更新以反映所有权的变更,TaskDialogLib 现在属于 Flatcode.net,这是我为所有开源项目创建的新网站和 GitHub 空间。库的版本现在是 1.1.0。请注意,XAML 命名空间从“http://github.com/sevenacids/TaskDialogLib”更改为“http://schemas.flatcode.net/2014/presentation”。
© . All rights reserved.