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

TDXML:带可视化任务对话框编辑器,基于 XML 的任务对话框

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.92/5 (64投票s)

2007 年 3 月 19 日

13分钟阅读

viewsIcon

104638

downloadIcon

1410

一个库和一个可视化编辑器,可以轻松地在 C++ 应用程序中构建和使用任务对话框。

目录

引言

如果阅读了我关于 Vista 任务对话框的前两篇文章(第一部分第二部分),您就会知道任务对话框是一项很棒的新 UI 功能,但使用起来可能有些繁琐。在 PDC 2005 的 PRS319 会话“构建在 Windows Vista 中外观出色的应用程序”中,演讲者演示了一个名为 TDPad 的工具,该工具由 shell 团队开发。TDPad 是一个简单的任务对话框编辑器,内部使用 XML 数据文件,并完成了一些繁重的工作,但将 TDPad 的输出集成到您的应用程序中仍然需要大量手动工作。

本文介绍了 TDXML(基于 XML 的任务对话框)库,与直接调用 TaskDialog()TaskDialogIndirect() API 相比,它更易于使用。它还包含一个可视化任务对话框编辑器,您可以使用它生成将用作 TDXML 输入的 XML。当然,您也可以自己创建 XML,但我认为您会发现可视化编辑器提供的即时反馈更加面向开发人员。

TDXML 库的设计目标是:

  • 完全数据驱动:您无需更改任何 C++ 代码即可更改任务对话框的外观。
  • 使用广泛可用的 XML 解析器:TDXML 使用 MSXML 解析器,该解析器存在于所有现代系统中。
  • 本地化:XML 可以作为文件与您的应用程序一起存储,也可以放在资源中。无论哪种方式,都可以轻松进行本地化。该库的数据驱动特性意味着您可以在不触碰或重新编译任何 C++ 代码的情况下本地化 XML。
  • 对调用者友好:要使用任务对话框,您只需初始化一个 C++ 对象并调用 DoModal(),这与您在 MFC 中使用 CDialog 和在 ATL 中使用 CDialogImpl 的方式类似。

本文是针对 Vista 的 RTM 版本撰写的,使用的是 Visual Studio 2005、WTL 7.5Windows SDK。有关在哪里下载这些组件的更多信息,请参阅第一篇 Vista Goodies 文章中的引言。TDXML 库使用了 CString_bstr_t 字符串包装器类;我仅在 VC 2005 的 WTL 应用程序中测试过该代码,但由于这些字符串类也可用于 MFC,因此 TDXML 库应该很容易集成到 MFC 应用程序中。

使用可视化编辑器

概述

任务对话框编辑器包含在演示下载的 TDEditor 项目中,您可以使用它来设置任务对话框,准确了解它在您的程序中的外观,然后生成一个您可以与 TDXML 库一起使用的 XML 文件。运行 TDEditor 时,您会看到四个页面,您可以在其中设置任务对话框的各种 UI 元素。以下是初始状态(编辑器截图显示为缩略图,以符合 CodeProject 对文章宽度的限制):

TDEditor 还显示了任务对话框外观的实时预览,使用了您在编辑器中输入的当前文本和设置。这是一个真正的任务对话框,而不是模拟,因此您可以确信所见即所得。这是运行 TDEditor 时任务对话框的初始状态:

在大多数情况下,TDEditor 会自动更新任务对话框以反映您在编辑器中所做的任何更改,但有些设置无法实现这一点。更改此类设置时,会在 Rebuild Dialog 按钮上添加一个信息图标,告知您需要重新构建对话框才能看到更改。如果您在任务对话框本身中进行了更改,例如单击展开/折叠按钮或选择单选按钮,则可以通过单击 Rebuild Dialog 来丢弃这些更改。单击下面的缩略图可查看显示实时编辑效果的示例动画:

一旦您对任务对话框的设置满意,就可以生成一个包含您在编辑器中输入的所有信息的 XML 文件。首先,单击 Save XML 以调出文件保存对话框。选择要使用的文件名,TDEditor 将为您生成一个 XML 文件。然后,您可以像本文后面所述那样,将该 XML 作为输入用于 TDXML。

添加文本和图标

Text and Icons 页面用于设置任务对话框的静态文本元素。旁边有复选框的元素是可选的;如果一个元素未选中,它将不会出现在任务对话框中。例外是 Caption 元素——如果您取消选中 Caption,Windows 将提供一个默认标题。Vista 中的默认标题文本是显示对话框的可执行文件的名称。

为了演示编辑器的实际操作,我将展示如何构建一个类似于我之前关于使用 TaskDialogIndirect 的文章中的对话框。设置好基本文本元素后,编辑器看起来是这样的:

到目前为止,任务对话框看起来是这样的:

请注意,页脚文本包含一个 <a> 标签,但它没有显示为超链接。现在不用担心,我们稍后会修复它。

添加按钮

Buttons 页面允许您指定要在任务对话框中显示的按钮。您可以添加六个内置按钮中的任何一个,或者添加任意数量的带有自定义文本的额外按钮。您还可以选择自定义按钮显示为常规按钮还是命令链接。

对于我们的示例对话框,我们将添加一个“关闭”按钮和两个自定义按钮。使用 Custom buttons 列表右侧的按钮来添加、删除或编辑对话框的自定义按钮。添加或编辑自定义按钮时,TDEditor 会显示一个对话框,您可以在其中输入按钮的详细信息:

如果您使用命令链接(如本示例所示),则可以在 Line 2 text 编辑框中输入其他文本。此文本将显示在按钮的第二行,字体较小。

您可以将 ID 更改为任何值,但请注意,较小的数字可能与内置按钮的 ID 冲突。默认 ID 为 1000,但大于 100 的任何数字都可以正常工作。TDEditor 会为每个额外按钮自动递增 ID,您也可以单击编辑器中的 Reset IDs 按钮为所有自定义按钮重新分配顺序 ID。

这是显示我们想要的任务对话框按钮的 Buttons 页面:

这是带有新按钮的任务对话框:

添加单选按钮

Radio Buttons 页面与 Buttons 页面的工作方式类似。由于没有内置的单选按钮,此页面仅包含一个 Custom buttons 列表和相关的控件。Default button 设置的工作方式也略有不同——它控制对话框首次显示时哪个单选按钮将被选中。如果您不希望任何单选按钮在开始时被选中,请取消选中 Default button 设置。

我们为虚构的 AntiFluff 程序创建的任务对话框通常不会有单选按钮,但为了说明此功能,我将添加两个按钮,如下所示:

这是任务对话框。请注意,根据编辑器中的 Default button 设置,第二个单选按钮被选中了。

设置对话框标志

Flags 页面非常简单,允许您切换一些任务对话框选项并设置几个附加属性。进度条样式还有计时器设置,默认为 50 ms。这是进度条更新之间的时间量;值越小,进度条移动得越快。

这是我们将为示例任务对话框设置的标志:

这是任务对话框的最终版本:

请注意,页脚现在包含一个超链接,因为我们设置了 Allow hyperlinks 标志。

使用 TDXML 显示任务对话框

您与 TDXML 库的接口是 CTaskDialogXML 类,它位于 TaskDialogXML.cppTaskDialogXML.h 文件中。使用 CTaskDialogXML 仅需两个步骤:

  1. 初始化对话框,告知它从哪里查找描述对话框内容的 XML。
  2. 如果初始化成功,请调用 DoModal() 来显示对话框。

有三种初始化对话框的方法,每种方法都从不同的源读取 XML 数据:

bool InitFromFile ( LPCTSTR szFilename )
调用 InitFromFile() 使用 XML 文件初始化对话框。szFilename 是文件的完整路径。
bool InitFromResource ( resourceID, resourceType, hModule )
调用 InitFromResource() 使用存储在资源中的数据初始化对话框。resourceID 可以是数字 ID 或字符串 ID。resourceType 默认为“TDXML”;您可以将 XML 数据文件放在此自定义资源类型中,以便在二进制文件的资源中将它们分组在一起。hModule 是包含资源的 HMODULE。如果您不指定 hModule,则使用当前进程的可执行文件。
bool InitFromVariant ( const _variant_t variantData )
InitFromVariant() 是其他两种初始化方法调用的函数,但如果您有一个包含数据源的 VARIANT_variant_t,则可以直接调用它。可以传递给 IXMLDOMDocument::load() 的任何数据都可以接受。

CTaskDialogXML 使用回调函数来实现某些功能,例如任务对话框 API 中未内置的带有 UAC 盾牌的按钮。这意味着如果您需要使用自己的回调函数,则必须设置 CTaskDialogXML 对象的两个成员,它们相当于 TASKDIALOGCONFIGpfCallbacklpCallbackData 成员。这些成员是 m_pfCallbackm_lpCallbackData;当您设置它们时,CTaskDialogXML 将像 TaskDialogIndirect() 一样调用您的回调。

初始化 CTaskDialogXML 对象后,调用 DoModal() 来显示对话框:

HRESULT DoModal ( HWND hwndParent )
如果对话框未初始化或初始化失败,DoModal() 将返回 E_FAIL,否则返回 TaskDialogIndirect() 的返回值。

如果 DoModal() 返回成功的 HRESULT,您可以通过读取 CTaskDialogXML 对象的三个公共成员来查看用户在对话框中的操作:

m_nButton
用户单击以关闭对话框的按钮的 ID。
m_nRadioButton
关闭对话框时选中的单选按钮的 ID。
m_bChecked
一个 BOOL,指示关闭对话框时复选框的状态。

如果对话框中未包含相应的 UI 元素,则后两个成员将不包含有意义的数据。有关这些值的设置方式的更多详细信息,请参阅 TaskDialogIndirect() 的文档。

可下载代码中的 TDXMLTester 项目包含三个任务对话框,并演示了使用 CTaskDialogXML 的各种方法。第一个对话框使用名为 testdialog.xml 的外部文件,该文件必须与 TDXMLTester.exe 在同一目录中。源下载中包含了一个示例文件,或者您可以自行创建和使用自己的文件。

这是 TDXMLTester 中使用外部 XML 文件的代码:

void CMainDlg::OnShowDialog1 (
  UINT uCode, int nID, HWND hwndCtrl )
{
HRESULT hr;
tdxml::CTaskDialogXML dlg;
TCHAR szTestFilename[MAX_PATH];
 
  GetModuleFileName ( NULL, szTestFilename,
                      _countof(szTestFilename) );
  PathRemoveFileSpec ( szTestFilename );
  PathAppend ( szTestFilename, _T("testdialog.xml") );
 
  if ( dlg.InitFromFile ( szTestFilename ) )
    hr = dlg.DoModal ( m_hWnd );
}

如您所见,这很简单。代码构建 XML 文件的完整路径,然后调用 InitFromFile() 并传递该路径。如果 InitFromFile() 成功,代码将调用 DoModal()

另外两个对话框使用 TDXMLTester.exe 中资源包含的 XML。如果您查看 TDXMLTester 资源,您会在“TDXML”类型下看到两个节点:

Test dialog 2 使用 IDR_CHNXML 资源,如下所示:

void CMainDlg::OnShowDialog2 (
  UINT uCode, int nID, HWND hwndCtrl )
{
HRESULT hr;
tdxml::CTaskDialogXML dlg;
 
  if ( dlg.InitFromResource ( IDR_CHNXML ) )
    hr = dlg.DoModal ( m_hWnd );
}

最后一个对话框非常相似,它只使用字符串资源 ID 而不是数字 ID。Dialog 3 还演示了如何使用自定义回调函数来处理用户单击超链接时发送的通知。

void CMainDlg::OnShowDialog3 (
  UINT uCode, int nID, HWND hwndCtrl )
{
HRESULT hr;
tdxml::CTaskDialogXML dlg;
 
  if ( dlg.InitFromResource ( _T("BIGTESTDIALOG") ))
    {
    // This dialog has a hyperlink, so we need to set
    // up a callback function that launches the URL
    // associated with the link.
    dlg.m_pfCallback = TDCallback;
    dlg.m_lpCallbackData = (LONG_PTR) this;
 
    hr = dlg.DoModal ( m_hWnd );
    }
}

其他 CTaskDialogXML 详细信息

CTaskDialogXML 包含一个公共成员 m_td,它是传递给 TaskDialogIndirect()TASKDIALOGCONFIG 结构。您可以在调用 DoModal() 之前修改此结构,如果您想执行任何额外的初始化或设置步骤。

CTaskDialogXML::DoModal() 有两种调用 TaskDialogIndirect() 的方式。默认行为是直接调用 API,这要求您的应用程序与 comctl32.dll 的 Vista 版本静态链接。如果您不想有此依赖项(例如,您希望程序也能在 XP 上运行),可以在 stdafx.h 中定义 TDXML_DONT_LINK_TO_API 符号。这使得 DoModal() 使用 GetProcAddress() 来获取 TaskDialogIndirect() 的地址。请注意,此方法仍然需要您的应用程序将 comctl32.dll 加载到其进程中,但如果操作系统不支持任务对话框,DoModal() 将只返回 E_FAIL

Bug 和功能待办列表

以下是我(已知)的 Bug 以及我为未来设想的一些功能。如果您有其他功能请求或 Bug 报告,请随时在此文章的论坛上发帖。

在任务对话框编辑器中:

  • 使用 Reset IDs 命令时,无法设置按钮的基 ID。
  • 编辑器不支持加载先前生成的 XML 文件。
  • 标准对话框导航键不起作用(Tab、Alt+字母等)。

在 TDXML 库中:

  • 用于构建任务对话框的 XML 不支持加载字符串或图标资源。您可以通过在调用 DoModal() 之前修改 m_td 成员来解决此限制。
  • 修复与使用该类与 MFC 相关的所有问题。
  • 加载资源的默认模块应根据应用程序是否使用 MFC 或 ATL 而有所不同。当前,它始终使用 NULL,这是 API 级别的默认值——有关更多信息,请参阅 FindResource() 文档。

进一步阅读

Vista 中的 C++ 实用功能:使用任务对话框显示友好消息

Vista 中的 C++ 实用功能:使用 TaskDialogIndirect 构建获取用户输入的对话框

面向开发人员的 Windows Vista - 第二部分 - 任务对话框详解。Kenny Kerr 有一篇非常长的帖子,讨论了任务对话框的更多功能,并提供了大量示例代码和 ATL 包装类。

MSDN 提供了关于新 Vista UI 指南的冗长页面。对于任务对话框,需要熟悉的是文本和语气页面。

版权和许可

本文是版权材料,©2007 Michael Dunn,保留所有权利。我知道这无法阻止人们在网上到处复制它,但我必须说出来。如果您有兴趣翻译本文,请给我发电子邮件告知我。我不认为会拒绝任何人翻译的许可,只是想知道翻译情况,以便在此处发布链接。

随本文提供的演示代码已发布到公共领域。我以这种方式发布是为了让代码造福所有人。(我不将文章本身公开,因为只在 CodeProject 上发布文章有助于提高我的知名度和 CodeProject 网站的流量。)如果您在自己的应用程序中使用演示代码,发送电子邮件告知我将不胜感激(仅为满足我对人们是否从我的代码中受益的好奇心),但不是必需的。在您自己的源代码中注明出处也表示赞赏,但不是必需的。

修订历史

2007 年 3 月 19 日:首次发布文章。

© . All rights reserved.