对话框模板,RegexTest by Paul DiLascia

来源:互联网 发布:安全事故数据统计 编辑:程序博客网 时间:2024/05/22 15:47

下载源代码:CAtWork0508.exe (279KB)
原文出处:Dialog Templates, RegexTest

  • 对话框模板
  • RegexTest


 我想用 MFC 和 C++ 创建一个基于对话框的程序(主窗口本身是个对话框)。我不想使用资源(.rc)文件,而是想在内存中动态创建对话框。我在 MSDN 中找到一些线索,但没有发现代码例子。我了解到 DLGTEMPLATE 和 DLGITEMTEMPLATE 结构以及 InitModalIndirect 函数或许可以用来创建模式对话框,但我不知道从何入手。请问如何不依赖资源文件动态创建对话框?

Thomas Zeitlberger

 从理论上讲,动态创建对话框很简单,但实际上那样做很危险。就是内存中创建正确的结构并调用一系列 Indirect 对话框创建函数之一:用 CDialog::CreateIndirect 创建非模式对话框,或者用 CDialog::InitModalIndirect 创建模式对话框(然后调用 DoModal 运行)。它们分别对应着 Win32 API 函数 ::CreateDialogIndirect 和 ::DialogBoxIndirect。不管用什么方法,你都得在内存中传递一个指向对话框模板的指针。
  从概念上讲,创建对话框模板很简单,就是在内存中建立并初始化相关结构。其具体细节是有讲究的,因为这些结构有点奇奇怪怪,很诡诈,你不得有一点差错,只要有一个字节的偏差,那么你的程序便会莫名其妙地垮掉。控件的位置和大小计算也会出现混乱,原因是对话框不使用像素,而是用对话框单位(units),它依赖对话框的字体。
  要完整地讨论包含所有类型控件的对话框模板不是本专栏所能胜任的。但我可以提供一个简单的例子,它至少包含一个控件。我写了一个类:CStringDialog,它显示一个对话框,请求用户输入一个字符串,
为了使用这个类,你只需实例化然后调用 Init 和 DoModal 即可:

CStringDialog dlg;dlg.Init(_T("Hi"), _T("Please enter your name:"));if (dlg.DoModal()==IDOK) {    CString name = dlg.m_str;    // do something with it...}

  CStringDialog 的样子和行为类似于所有基于对话框资源的 CDialog 派生类,所不同的是该对话框用其自身模板在内存中动态生成。
  那么对话框模板到底是个什么东西呢?对话框模板其实就是一个描述对话框的内存结构。这个模板之所以复杂并容易出错,是因为它并非像 CREATESTRUCT 和 WNDCLASS 一样是个定长结构。它是一个变长结构,其中包含有定长结构元素 DLGTEMPLATE 以及 DLGITEMTEMPLATE 结构数组,其每个数组元素对应着一个对话框控件项。DLGTEMPLATE 和 DLGITEMTEMPLATE 两者都包含一些跟在 C 结构后面非常很特别的变长域。这些结构如 Figure 2 所示,
  对话框模板有点像汇编语言编程手册中的内容,现在就让我们穿上蹩脚的工作制服,立即从 DLGTEMPLATE 开始吧。
  假设你分配了一块足够大的内存来存放整个对话框模板,首先要做的事情就填写 DLGTEMPLATE 结构域。这一部分不难:

WORD* pTempl = new WORD[1024];DLGTEMPLATE& dt = *((DLGTEMPLATE*)pTempl);dt.style = WS_POPUPWINDOW|DS_MODALFRAME|WS_DLGFRAME;dt.cdit = 3; // # dlg itemsdt.x = 100; // in dlg units// etc.

  DLGTEMPLATE 结构域是自扩展的,对此我不再做进一步说明。紧跟着该结构后面的域是变长域:菜单,对话框类和标题。每一项都不能超过一个 WORD。它可以是一个空结尾的 Unicode 字符串以标示某个 MENU 资源的名字,对话框类名或标题。此外,菜单和类名可以用特殊值 0xFFFF 后跟一个 16位 的 ID——即可以是菜单资源的 ID,也可以是预定义系统窗口类的序数。在大多数情况下,类名都应该使用 0x0000(空串),它告诉 Windows 操作系统使用默认的对话框类(#32770)。多数对话框都没有菜单,所以菜单也是 0x0000(空串)。在代码中是这样写的:

*pTempl++ = 0; // 菜单 (无)*pTempl++ = 0; // 对话框类 (使用标准的对话框类)

接下来是标题,一个空结尾的 Unicode 字符串:

USES_CONVERSION;LPCWSTR wszText = T2W(_T("My Dialog"));wcscpy((WCHAR*)pTempl, wszText);pTempl += wcslen(wszText)+1;

  这段代码支持 Unicode 或者 ASCII,因为定义了 _UNICODE,T2W是一个串转换宏。不要忘了增加模板指针 pTempl 的增量值,将其指到串后面的下一个 WORD。如果对话框具有 DS_SETFON 式样,在第四个字段:16位的字体大小后跟 Unicode 字体名,例如:“Verdana”。
  最后,我要指出对话框模板有一个扩展版本 DLGTEMPLATEEX,它可以让你指定更多的域,如字体点数和重量、是否用斜体、字符集、字体名。想了解更多信息请参考文档。这里我仅描述一个简单版本,因为通过在 OnInitDialog 处理例程中调用 SetFont 来设置字体是很容易的事情。(对于对话框中的控件项也有一个 DLGITEMTEMPLATEEX 扩展版本)。
  讲了这么多 DLGTEMPLATE。下面该看看控件。对话框中的每个控件项都是通过一个模板来描述的(DLGITEMTEMPLATE),其值不能超过一个 DWORD:

pTempl = AlignDWORD(pTempl);DLGITEMTEMPLATE& it = *((DLGITEMTEMPLATE*)pTempl);it.x = 0;it.y = 0;// etc.

  与 DLGTEMPLATE 类似,DLGITEMTEMPLATE 结构后面有三个变长域。即类名,文本和“”创建数据(creation data)。类名也是空结尾的 Unicode 字符串指定窗口类名(例如,“SysListView32”或者“MyFancyControl”),或者 0xFFFF 后跟特定的原子码之一,这些编码如 Figure 4 所示,它们都用于标准的预定义系统控件。例如,下面的代码示范了如何创建一个静态的文本控件:

// class immediately after DLGITEMTEMPLATE*pTempl++ = 0xFFFF; // next WORD is atom:*pTempl++ = 0x0082; // static control

  类名后面是标题。它既可以是 Unicode 字符串,也可以是特定的 0xFFFF 后跟 16位的资源 ID。你还可以用 0xFFFF + ID 的格式来为某个具备 SS_ICON 或 SS_BITMAP 式样的静态控件指定一个图标或位图。CStringDialog 使用字符串形式来创建其提示:

USES_CONVERSION;LPCWSTR wszTest = T2W(_T("My Dialog"));int maxlen = /* don''t overflow! */wcsncpy((WCHAR*)pTempl, wszText, maxlen); pTempl += wcslen(wszText)+1;

  最后,“creation data”可以是任何你想要的数据。第一个 WORD 是数据长度,如果没有数据,其值可以是零。Windows 用 LPARAM 将一个指向数据的指针传递给 WM_INITDIALOG(模式对话框)或者 WM_CREATE(无模式对话框)。在此我不推荐使用创建数据,因为将任何你想要的数据成员添加到对话框类中,并用对话框的构造函数或 OnInitDialog 处理例程初始化它们的做法要容易得多。但你仍然得提供一个 0 WORD 来告诉 Windows 没有创建数据:

*pTempl++ = 0; // no creation data

  一旦你填充完指定的 DLGITEMTEMPLATE 数据,便可如法炮制下一个对话框控件,直到完成所有的控件。接着确保 DLGTEMPLATE::cdit 指定正确的控件总数。为了简化建立对话框模板的过程(使之尽量少出错)。我写了一个辅助类,CDlgTemplateBuilder。CStringDialog 用这个类可以一步到位建立对话框:

// in CStringDialog::InitCDlgTemplateBuilder& dtb = m_dtb;DLGTEMPLATE* pTempl = dtb.Begin(...);dtb.AddItem(...);dtb.AddItem(...);dtb.AddItem(...);InitModalIndirect(pTempl, ...);

  我已将细节模糊而突出主要思路:调用一次 Begin,然后针对每个控件调用一次 AddItem。CDlgTemplateBuilder::Begin 生成 DLGTEMPLATE 并且每次调用 AddItem 生成另一个 DLGITEMTEMPLATE。CDlgTemplateBuilder 在自己的内存缓冲里生成模板并在每次添加控件时自动增加 DLGTEMPLATE::cdit(控件项数目)。CDlgTemplateBuilder 具备辅助函数 AlignDWORD 和 AddText 以确保数据对齐和实现正确的字符串转换。有关细节自己下载源代码细细琢磨吧。
  我前面说过对话框使用对话框单位,而非像素。DLGTEMPLATE 和 DLGITEMTEMPLATE 两个都包含 x,y,cx h和 cy 成员来指定对话框或控件项的位置和大小。这些值都是对话框单位。每个水平方向的对话框单位是四分之一的基本单位,而每个垂直方向的对话框单位是八分之一的基本单位。一个基本单位是对话框中一个字符宽度和高度的平均值,并且依赖于对话框的字体。是不是很痛苦啊!没错,但凭心而论,这个想法是值得赞誉的:对话框单位使你的对话框外观独立于其字体。所以不管你用大字体也好,小字体也好,所有控件的相对位置是不会变的,一切都显示正常。Windows 有一个特别的函数叫做 MapDialogRect,用来将对话框单位转换为像素;令人惊讶的是却没有反向转换函数,而这正是生成模板所需要的——但你可以用如下公式:

CSize base = ::GetDialogBaseUnits();xDlg = MulDiv(xPixel, 4, base.cx);yDlg = MulDiv(yPixel, 8, base.cy);

  对于 CStringDialog 来说,我懒得去做这些事情,而是试验性找到正确的值显示出如图 Figure 1 所示的对话框。更复杂的实现得检查提示串的长度,或允许调用这指定尺寸。如果处理这些对话框单位让你头痛,那么你就创建大小和位置都是 0 的控件得了,然后实现 OnSize  处理例程将控件移到适合的像素位置。你的对话框得从 OnInitDialog 向自身发送 WM_SIZE 消息以确保第一次显示时控件被正确定位。

  最后,我是如何让 CStringDialog 显示如 Figure 1 所示的问号的呢?CStringDialog::Init 让你指定提示图标。默认是 IDI_QUESTION。但 IDI_QUESTION 是一个内建的图标,不是来自应用程序资源文件的图标。如果你指定一个对话框模板中的资源 ID,Windows 期望它在资源文件中。那么我如何让 Windows 改用系统图标呢?
  当然,话虽如此,CStringDialog 检查图标资源 ID,看看值是否大于 IDI_APPLICATION,也就是第一个系统图标的 ID。如果该图标 ID 在系统 ID 范围之内,CStringDialog 通过调用 ::LoadIcon 来加载它,此时 hInstance 置为 NULL(用于系统图标)并在 数据成员 m_hIcon 中保存加载的 HICON。然后 CStringDialog用 0xFFFF + nResID 格式(nResID=0)来构造对话框模板。这导致 Windows 创建一个静态图标,但并非实际的图标,然后,CStringDialog 在 OnInitDialog 中才设置实际图标:

// in CStringDialog::OnInitDialog()if (m_hIcon) {    CStatic* pStatic = (CStatic*)GetDlgItem(IDICON);    pStatic->SetIcon(m_hIcon);}

  这样一来,你可以将任何 IDI_XXX 形式的图标 IDs 传递给 CStringDialog::Init。你还能用自己的图标,只要其 ID 小于 IDI_APPLICATION = 32512。具体细节请参考源代码。

原创粉丝点击