10.对话框管理器的内部工作
我认为许多对于对话框管理器的困惑,来自没有真正的理解它是怎么工作的.没有那么糟糕.在一些热身讨论之后,我走进对话框模板的历史,把它作为一个基础来理解对话框时怎么建立的,然后跟踪对话框消息循环,分成几个章节以便浏览.
关于对话框过程
一个对话框过程真的没有多少东西.对于每条消息,你可以选择是否处理它,就像一个窗口过程那样.但不同的是,表达这个选择是通过返回值来完成的.
对话框过程的返回值
由于某些原因,对话框过程的返回值的方式迷惑人.我将试着解释它的不同之处.
对话框过程的这个技巧是为了实现它确实需要返回两条信息:
-消息是否被处理了?
-如果"是",结果是什么?
因为有两条消息要返回,但是C的函数只有一个返回值,这就需要有某个另外的方法来返回信息的第2部分.
对话框过程的返回值表示消息是否被处理了.信息的第2部分(结果是什么)隐藏在DWLP_MSGRESULT这个window long里面.
换句话说,DefDlgProc像这样处理事务:(原文DefDlgProc goes something like this:)
- LRESULT CALLBACK DefDlgProc(HWND hdlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
- {
- DLGPROC dp = (DLGPROC)GetWindowLongPtr(hdlg, DWLP_DLGPROC);
- SetWindowLongPtr(hdlg, DWLP_MSGRESULT, 0);
- INT_PTR fResult = dp(hdlg, uMsg, wParam, lParam);
- if (fResult) return GetWindowLongPtr(hdlg, DWLP_MSGRESULT);
- else ...做默认的事...
- }
如果你返回非0值,你使用SetwindowLongPtr(hdlg, DWLP_MSGRESULT, value)所设置的值被当做是消息结果.
(Old-timers(不会翻了...)可能想知道用GetwindowLong和DWL_MSGRESULT会怎么样.在64位Windows的介绍中,GetwindowLong获得了指针的尺寸,像GetWindowLongPtr一样,他操作一个跟内部指针(原文native pointer)尺寸一样的整数.因为窗口过程的返回值也变64位了,储存对话框过程返回值的window bytes的字段名,也从DWL_MSGRESULT变成了DWLP_MSGRESULT, P表示你应该用SetwindowLongPtr而不是SetwindowLong.如果这严重的动摇了你的系统,你可以忽略这个P,并且要记着以后要学习64位编程.)
举个例,很多WM_NOTIFY通知允许你返回true来重载默认行为.要阻止一个listview的标签被编辑,你可以在LVN_BEGINLABELEDIT通知中返回true.如果你要在对话框过程中这么做,你必须分为两步:
- SetWindowLongPtr(hdlg, DWLP_MSGRESULT, TRUE);
- return TRUE;
第2行设置对话框过程的返回值,是为了告诉DefDlgProc这条消息已经被处理了,默认的处理被阻止.第1行告诉DefDlgProc要返回什么值到消息的发送者(listview控件).如果你忘了任何1步,期望的值将不能到达listview控件.
注意DefDlgProc在发送调用dp之前就把DWLP_MSGRESULT设为0.这么一来,如果对话框过程没有明确的设置消息结果,那结果就是0.
这也强调了:在调用SetWindowLongPtr之后,要立即从对话框过程中返回,而不是过一会.如果你在设置返回值/返回ture之间做了任何事,就可能触发一条消息,并发送到窗口过程,这将把消息结果置0.(译者:覆盖是肯定的,置0倒不一定.)
警告:少量特殊的消息不遵循这个规则.这个列表在MSDN的DialogProc条目中给出.为什么要有例外?因为当对话框管理器首次被设计时,这些消息的特殊处理就被确定了,这将使对话框过程更容易写.因为你不需要用DWLP_MSGRESULT来返回消息结果.幸好,从那以后,没有人添加新的例外.记住这些特例所耗费的精神要比少写一个SetWindowLongPtr所省下的精神要多.(译者:这么别扭呢,作者是说"得不偿失"吗?)
译者:作者说的"special messages"用复数,似乎暗示特殊的消息不止一条,但是MSDN里只提到了一条WM_INITDIALOG.