《windows程序设计》勘误(备忘ing)待续

时间:2021-07-01 08:07:54

http://www.jasondoucette.com/books/pw5/pw5errata.html

上面是windows编程圣经《windows程序设计》的勘误表链接

 

暂且拣几个个人认为最重要的几个勘误简单翻译下,做个备忘哈

 

勘误1: GetMessage() 有可能会返回失败,所以应该总是检查其返回值

通常的写法如下

 

while( GetMessage( &msg, NULL, 0, 0 ) )

{

TranslateMessage( &msg );
DispatchMessage( &msg );

}

其实这样写是错误的。事实上这个函数的返回值可能是 非0、0、或是-1。

当返回-1时,说明程序发生了致命的错误,然而上面的循环判断,-1则会被当成TRUE来处理,这个消息循环并不会结束,而且还会继续Translate和Dispatch msg结构体变量中的message息,然而这个message却有可能正是上一次处理过的消息(注意啊,这个只是有可能!具体包含是什么,其实是未文档的行为)

正确的写法:

BOOL bRet;

while ( (bRet = GetMessage( &msg, NULL, 0, 0 )) != 0)
{
if (bRet == -1)
{
// handle the error and possibly exit
}
else
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}

 

勘误2: CreateWindow有可能会返回失败,所以要检查其返回值


勘误3: 对于窗口类的background brush的设置

对于所有在WM_PAINT消息处理代码中重绘整个客户区的程序而言(就是说,所有不用ROP(raster operations的重绘(repaint)操作)。

当窗口大小发生变动时,windows会自动用窗口类的background brush来填充窗口的客户区。它通常是通过给窗口发送一个WM_ERASEBKGND的消息,并且DefWindowProc函数对这个消息的默认处理,就是用WNDCLASS结构体中hbrBackground成员所指定的background brush来擦除(erase)窗口的背景。如果这成员的值为NULL,那么就不擦除(erase)窗口背景(尽管应用程序可以通过在处理WM_ERASEBKGROUND消息中手动将背景擦除掉)。

 

所以说,对于那些在WM_PANIT消息的处理代码中自己填充客户区的应用程序而言,这将会导致程序变慢,因为这会导致窗口客户区背景没必要的闪动(flicker)。

 

正确的写法:

wndclass.hbrBackground = NULL ;


勘误4:模拟按(press)Button的消息

书中的做法:
SendMessage(hdlg, WM_COMMAND, IDC_MYBUTTON, 0) ;
hDlg是Button的父窗口句柄,IDC_MYBUTTON则是Button的标识符(identifier)。
这行代码能使,但并不完全正确(proper)。这不是好的代码,因为它混淆了实际会发生的情况。可以如下来模拟这个SendMessage调用流程:
用户在某个Button上点击并释放了鼠标按键。这个Button首先会在它自己的窗口过程(procedure)中处理这个消息,然后再把这个消息发送给它的父窗口(parent)。当它的父窗口得知这个Button被点击(clicked)了,就可以做一些适当的响应。Button发送给其父窗口的是一个WM_COMMAND的通知消息,其中wparam的高位字(high-order word)指定的是通知吗(notification code), 低位字指定的则是这个button的控件ID,而lparam保存的则是这个button的句柄。
所以,我们应该通过以下方式来调用它:

SendMessage (
hdlg, // handle to window we are sending message to
WM_COMMAND, // message we are sending
MAKEWPARAM(IDC_MYBUTTON, BN_CLICKED), // control identifier, and notification code
GetDlgItem(hdlg, IDC_MYBUTTON)); // handle to window that is sending the message
我们来看看 Charles Petzoldd的方法有什么不同:
首先,他并没有将这两个信息放到一起来传给wparam参数,他仅仅是将控件ID给传进去了。你可能注意到了,因为BN_CLICKED被定义为0,所以MAKEWPARAM调用产生的结果恰恰就是控件的ID。所以说,他的代码只是逻辑上(logically)正确。
其次,他给lparam参数传的是NULL值,而实际上应该传的是button的句柄才对。

其实,还有比上面的代码更为简单的方法来模拟用户点击button,那就是使用BM_CLICK消息。这个消息是通过给button发送WM_LBUTTONDOWN和WM_LBUTTONUP消息来模拟用户鼠标点击一个button的事件,此时,button会认为它已经被按下了(pressed),并且会给它的父窗口发送一个我们之前所述的BN_CLICKED通知消息.
而要发送这个BM_CLICK消息仅仅需要这个button的句柄就可以了,代码如下:
SendMessage(
GetDlgItem(hdlg, IDC_MYBUTTON), // handle to button to be pressed
BM_CLICK, // message ID
0, // wParam = 0; not used, must be zero
0); // lParam = 0; not used, must be zero
但是这种做法还有一个缺点,通过发送鼠标消息给button来期望button对这个消息作出对应的响应,这种做法可以使button发送期望的BN_CLICKED通知消息给其父窗口,但也会使这个button自动获得焦点(focus)。
譬如,当在一个button被点击的事件处理代码中,想要给另外的button发送鼠标点击消息来调用它的消息处理代码,结果就有可能会发生点击了一个button,但焦点却跑到了另一个button上的情况


勘误5:WM_PAINT消息处理
书中关于WM_PAINT消息处理代码还漏了一部分信息。当调用了标识了RDW_INTERNALPAINT的RedrawWindow函数,窗口就有可能会收到内部的(internal)paint消息。在这种情况下,窗口就可能没有 更新区域(update region)。所以,应用程序首先应该调用GetUpdateRect来判断窗口是否拥有一个更新区域(update region),如果函数返回0,那么就不应该调用BeginPaint和EndPaint函数。

 

勘误6:不正确的子类化(subclass)实现

在被子类化的窗口销毁之前,应该先移除你的窗口子类。如果你的子类分配了内存,或是需要在结束时释放一些资源,那么就需要显示清理所有子类化的窗口。