关键词:UpdateLayeredWindow、WM_SIZE、工具提示(tooltip)、线程、SetWindowPos
一个应用程序,启动后创建一个隐藏的主窗口,并创建一个总是显示于桌面的顶级窗口(简称此窗口为 P),P 窗口客户区的初始大小为 48×48,并根据此范围创建了一个关联于 P 窗口的工具提示窗口(简称为 T)。在后续的代码中,根据对某一标志的判断结果,可能会向 P 窗口设定一幅位图,并根据位图自动改变窗口的大小以及透明度(此时窗口必须改变为层次窗口)。
但程序运行的结果是如果设置了位图,则工具提示将永远不能显示。出于其他一些原因,导致另外一个程序员将此窗口移到了另外单独的线程中去创建,结果一切正常!
后一种现象严重地误导了我,使我认为此问题和多线程有关。经过一天的跟踪调试,发现如果将改变为层次窗口的步骤省略,则无论是否将之置于单独线程,工具提示均可以正常出现。于是开始仔细审查设置层次窗口相关的代码,在其中的一个对 SetWindowPos 函数的调用提示了我(程序员写这条语句的初衷是为了手动调整窗口的大小以适应位图),那就是,窗口的大小改变了,我却没有看到任何根据新的客户区大小调整工具提示显示矩形的代码。带着莫大的兴奋将 WM_SIZE 的响应代码加上,果然工具提示可以显示了!
可是有一点问题,按照程序员的初衷,窗口原始大小就有 48×48,那么即使改变窗口大小时没有更新工具提示的显示区域,当鼠标停留于窗口左上 48×48 范围内时也应该显示提示才对啊。我不得不在第一次设定工具提示显示范围的地方加了断点,运行程序,果然不出所料,此时的初始值为 0x0,再一看函数调用栈,原来是在程序员在 P 窗口的 WM_CREATE 响应中,此时调用 GetClientRect,返回了一个空空的矩形。
在查找问题的过程中顺便仔细看了一下 MSDN 中关于 UpdateLayeredWindow() 函数的说明:
The UpdateLayeredWindow function updates the position, size, shape, content, and translucency of a layered window.
译文:UpdateLayeredWindow 函数可以更新层次窗口的位置、大小、形状、内容以及透明度。
可见此函数是 MoveWindow、SetWndowPos、SetWindowRgn 等函数的强大组合。由此而使我认为原来代码中调用此函数之前对 SetWindowPos 函数的调用没有必要,然而试验的结果却令我小吃一惊:去掉 SetWindowPos 的调用,在使用 UpdateLayeredWindow 改变窗口大小后,窗口本身不会收到 WM_SIZE 消息(我怀疑改变位置后同样也不会收到 WM_MOVE 消息,但没用测试)。这显然是此 API 的一个大缺陷。
问题虽然找到了,可是却仍然有疑问没有答案。
1、初始设定的工具提示显示范围为 0x0,那么按道理即使不对 P 执行向层次窗口的转换,在 48×48 的窗口中也不应该能显示提示才对,可是事实上是可以显示的。
2、把 P 窗口放在独立的线程中,即使不对 WM_SIZE 消息进行响应,工具提示却能够“感知”宿主窗口客户区的变化而导致显示正常,不知何故。
直觉上认为以上两个问题其实是同一个,请行家指教。