WIN32汇编语言教程:第09章 通用控件 · 9.5 窗口的子类化(1)
9.5.1 什么是窗口的子类化
在使用控件的过程中,常常会遇到这样一种情况:我们需要一种窗口,它和某种现成的控件提供的功能很相似,如果使用现成控件的话,那么控件几乎能提供所有需要的功能,仅我们要求的某个细节无法实现。举例来说,要编写一个16进制与10进制的转换程序,程序中需要两个编辑控件来输入数值,输入10进制数值可以使用现成的Edit控件,只要指定ES_NUMBER风格就能让编辑框只能输入数字0~9,但输入16进制数值的时候就不行了,因为指定ES_NUMBER风格的话就无法输入A~F,不指定的话用户就可能输入0~9和A~F之外的东西,那么该如何处理呢?
解决的办法有两种,第一种当然是自己创建一个窗口类,然后在自己的窗口过程中完成所有的功能,这显然是一项费时又费力的工作,因为我们几乎要自己重新写一遍Edit控件的全部功能;第二种方法就是使用本节要介绍的窗口子类化,窗口子类化最适合做的就是这一类工作。
窗口子类化的含义是接管被子类化的控件窗口,以达到对它进行控制的目的。虽然控件的窗口过程被封装在Windows内部,无法对它进行直接修改,但只要能截获Windows给控件的窗口过程发送的消息,就能够控制控件窗口。以上面的要求为例,只要截获Windows向编辑控件发送的WM_CHAR消息,就能够根据需要丢弃包含非16进制字符的WM_CHAR消息,只把包含16进制字符的WM_CHAR转发给控件的窗口过程,这样编辑控件将根本收不到16进制字符之外的字符,我们的要求也就达到了。
控件窗口子类化的流程如图9.8所示。
子类化的操作并不局限于控件窗口,实际上任何窗口都可以子类化。但是对于应用程序自身使用的窗口类来说,它的控制权本来就是100%属于应用程序自身的,要实现某种功能就直接修改源代码好了,没有必要再进行一个子类化的过程,所以子类化的操作往往是对“黑匣子”类型的控件窗口进行的。
图9.8 窗口子类化的工作原理
9.5.2 窗口子类化的实现
窗口子类化的要点是截获窗口的窗口过程,如何实现这一点呢?每个窗口的内部都保存有它所属的窗口类的WNDCLASSEX 结构,结构中的lpfnWndProc字段指出了窗口过程的地址,如果能用自己的窗口过程地址来替换这个地址,那么Windows就会把消息发送到自定义的窗口过程中来了。通过调用函数SetWindowLong可以实现这个功能,SetWindowLong函数的用法是这样的:
invoke SetWindowLong,hWnd,nIndex,dwNewLong mov dwOldLong
hWnd参数指定要子类化窗口的窗口句柄,nIndex参数指定需要修改窗口的哪个属性,它可以是以下的取值:
● GWL_EXSTYLE——窗口的扩展风格。
● GWL_STYLE——窗口风格。
● GWL_WNDPROC——窗口过程地址(这就是我们需要的)。
● GWL_HINSTANCE——窗口所属的模块实例句柄。
● GWL_ID——窗口ID。
● GWL_USERDATA——窗口附带的32位自定义数值。
dwNewLong参数指定新的属性值。如果nIndex为GWL_WNDPROC,dwNewLong表示新的窗口过程地址;如果nIndex为GWL_STYLE,dwNewLong则表示新的窗口风格,依此类推。函数的返回值是指定属性的原先数值。当函数用于窗口子类化的时候,在nIndex参数中使用GWL_WNDPROC,以便将窗口过程地址设置到自定义的子程序中,这时函数返回的是控件窗口原来的窗口过程地址,由于窗口子类化的出发点就是为了尽量使用控件窗口原有的功能,程序为了“偷懒”而不去处理的大部分消息还要靠原来的窗口过程来处理,所以这个地址必须被保存下来。
让我们通过一个简单的例子来演示窗口子类化的实现过程,程序实现的就是前面介绍的16进制与10进制转换的程序,源代码位于所附光盘的Chapter09\SubClass目录中。程序首先在资源脚本文件SubClass.rc中定义了一个对话框,对话框中包括两个编辑控件,IDC_DEC用来输入10进制数值,它包含ES_NUMBER风格,只能输入0~9的数值;而IDC_HEX用来输入16进制数值,代码如下:
//################################################################## #include <resource.h> //################################################################## #define ICO_MAIN 1000 #define DLG_MAIN 1000 #define IDC_HEX 1001 #define IDC_DEC 1002 //################################################################## ICO_MAIN ICON "Main.ico" //################################################################## DLG_MAIN DIALOG 107, 102, 129, 42 STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU CAPTION "Hex <> Dec" FONT 9, "宋体" { LTEXT "Hex", -1, 7, 9, 15, 8 EDITTEXT IDC_HEX, 27, 7, 94, 12 LTEXT "Dec", -1, 7, 26, 15, 8 EDITTEXT IDC_DEC, 27, 24, 94, 12, ES_NUMBER }
上页:第09章 通用控件 · 9.4 使用Richedit控件(9) 下页:第09章 通用控件 · 9.5 窗口的子类化(2)