对话框管理器第二章:创建框架窗口

对话框管理器第二章:创建框架窗口

作者:BlogUpdater |  时间:2022-07-24 |  浏览:196 |  评论已关闭 条评论

对话框模板包含了对话框外观的描述,所以对话框管理器只是简单地遍历模板并按照模板的描述来创建对话框。这个过程十分简单和直接,对话框管理器没有太多自己的决策空间,它只是按照模板说的做而已。
为了简单起见,我会假设这里说的对话框管理器是一个扩展版本的对话框模板。它是经典的DLGTEMPLATE的超集,所以我讲述起来就比较通用。
另外,我还会跳过一些比较特殊的部分(例如WM_ENTERIDLE消息),因为它们和我要将的主要部分关系不大。

同时,因为篇幅的原因,我也会将错误处理略过。

最后,我假设你已经了解各种对话框模板的结构并忽略模板语句解析方面的问题。

好了,可以开始了。

首要任务是研究对话框样式并将 DS_* 样式转换为 WS_* 和 WS_EX_* 样式,如下图所示:

问题:为什么定义了DS_CONTROL风格会导致去掉WS_CAPTION和WS_SYSMENU呢?
答案:通过简单地添加一个样式标志,使人们更容易将现有对话框转换为 DS_CONTROL 子对话框。
如果对话框模板中包含菜单,则从作为创建参数的一部分传递的实例句柄来加载菜单。
hmenu = LoadMenu(hinst, );

这是对话创建中的一个常用方法:传递给对话创建函数的实例句柄用于对话创建期间的所有资源相关活动。

获取对话框字体的算法如下:

请注意,DS_SETFONT 优先于 DS_FIXEDFONT。

一旦对话框管理器拥有字体,就会对其进行测量,以便可以使用其尺寸将对话框单元 (DLU) 转换为像素。 对话框布局中的所有内容都在 DLU 中完成。 如果您忘记了将 DLU 转换为像素的公式,这里有个提醒。 具体公式如下:

// 4 xdlu = 1 average character width
// 8 ydlu = 1 average character height
#define XDLU2Pix(xdlu) MulDiv(xdlu, AveCharWidth, 4)
#define YDLU2Pix(ydlu) MulDiv(ydlu, AveCharHeight, 8)

对话框大小来自模板:
cxDlg = XDLU2Pix(DialogTemplate.cx);
cyDlg = YDLU2Pix(DialogTemplate.cy);

模板中的对话框大小是客户区的大小,所以我们也需要在非客户区添加。
RECT rcAdjust = { 0, 0, cxDlg, cyDlg };
AdjustWindowRectEx(&rcAdjust, dwStyle, hmenu != NULL, dwExStyle);
int cxDlg = rcAdjust.right – rcAdjust.left;
int cyDlg = rcAdjust.bottom – rcAdjust.top;

我怎么知道它是客户区而不是包括非客户区在内的整个窗口? 因为如果是全窗矩形,就不可能设计对话框! 模板设计者不知道最终用户的系统将设置为哪些非客户端指标,因此无法在设计时考虑它。

(这是更一般规则的一个特例:如果你不确定某件事是否真实,问问自己,“如果它是真实的,世界会是什么样子?”如果你发现一个明显错误的逻辑结果, 那么你刚刚[通过矛盾]证明你所考虑的事情确实不是真的。这是一个重要的逻辑原理,我会一次又一次地回到。其实你几天前就看到了。)

假设 DS_ABSALIGN 样式未设置,对话框模板中给出的坐标是相对于对话框的父级的。
POINT pt = { XDLU2Pix(DialogTemplate.x),YDLU2Pix(DialogTemplate.y) };
ClientToScreen(hwndParent, &pt);

但是如果调用者传递了 hwndParent = NULL 怎么办? 在这种情况下,对话框位置相对于主屏幕的左上角,但不要这样做。

> 在多显示器系统上,它会将对话框放在主显示器上,即使你的程序正在辅助显示器上运行。

> 用户可能将他们的任务栏停靠在屏幕的顶部或左侧边缘,这将覆盖你的对话框。

> 即使在单显示器系统上,你的程序也可能在屏幕的右下角运行。 将对话放在左上角不会在两者之间建立有意义的联系。

> 如果你的程序的两个副本正在运行,它们的对话框将精确地相互覆盖。 我们在之前的文章中看到了这种情况的危险。

故事的寓意:始终传递一个hwndParent窗口,以便对话框出现在相对于程序的其余部分有意义的位置。 (也不要只抓住 GetDesktopWindow!)

好的,我们现在已经准备好创建对话框:我们有它的类、它的字体、它的菜单、它的大小和位置等。

哦等等,我们必须处理前面讨论过的对话框创建的微妙之处:对话框总是在最初隐藏创建的。
BOOL fWasVisible = dwStyle & WS_VISIBLE;
dwStyle &= ~WS_VISIBLE;

对话框类和标题来自模板。 几乎每个人都只使用默认对话框类,尽管我在之前的文章中解释了如何使用自定义对话框类。

好的,现在我们有了创建窗口所需的信息。
HWND hdlg = CreateWindowEx(dwExStyle, pszClass,
pszCaption, dwStyle & 0xFFFF0000, pt.x, pt.y,
cxDlg, cyDlg, hwndParent, hmenu, hinst, NULL);

请注意,我们过滤掉了所有低样式位(每个类),因为我们已经将 DS_* 样式转换为“真实”样式。

这就是为什么你的对话过程没有得到像 WM_CREATE 这样的窗口创建消息的原因。 在创建框架时,对话过程还没有进入画面。 只有在框架创建之后,对话管理器才能附加对话过程。
// Set the dialog procedure
SetWindowLongPtr(hdlg, DWLP_DLGPROC, (LPARAM)lpDlgProc);

对话框管理器在这一点上做了更多的摆弄,基于对话框模板样式。 模板可能要求提供窗口上下文帮助 ID。 如果模板没有指定允许调整大小、最大化或最小化的窗口样式,则相关的菜单项将从对话框的系统菜单中删除。

接下来是设置字体:
SetWindowFont(hdlg, hf, FALSE);

这就是为什么你的对话过程收到的第一条消息恰好是 WM_SETFONT:它是在设置 DWLP_DLGPROC 之后发送的第一条消息。 当然,这种行为将来可能会改变; 你不应该依赖消息排序。

好的,对话框架现在已经创建完成了。 下一步:创建控件。

总结
拖拖控件它确实简单,但是操作系统实现它的底层原理还真是不那么简单。
你可以:
> 忽略这些复杂的,繁琐的东西,继续”接着奏乐接着舞”。
> 苦其心志,深入研究这些细枝末节的东西。
好了,选择权给到你这边了。

最后
Raymond Chen的《The Old New Thing》是我非常喜欢的博客之一,里面有很多关于Windows的小知识,对于广大Windows平台开发者来说,确实十分有帮助。
本文来自:《The dialog manager, part 2: Creating the frame window》

最近我写了个东西
正如你们所知道的,拓扑梅尔智慧办公平台(Topomel Box)是一款绿色软件,主要面向经常使用电脑的朋友。它提供了各种提升办公效率的小功能,同时操作上尽可能地简单方便。
我想:你值得拥有。

评论已关闭。