WIN32汇编语言教程:第09章 通用控件 · 9.5 窗口的子类化(3)
end start
程序运行后将显示如图9.9所示的对话框,上面的编辑控件是经过子类化的,只能输入16进制字符,当输入字母a~f的时候,不管输入的是大写还是小写字母,都会被控件转换成大写字母。下面的编辑框是ES_NUMBER风格的,可以输入数字。不管在哪个编辑框中输入数值,程序会马上进行转换并将结果在另一个编辑框中显示出来。
图9.9 窗口子类化例子的运行界面
程序在对话框的初始化消息WM_INITDIALOG中对这两个编辑控件发送EM_LIMITTEXT消息,以此限制能够输入的最大字符长度,接下来程序通过GetDlgItem获取IDC_HEX编辑框的窗口句柄,并使用SetWindowLong函数将编辑框的新窗口过程设置到_ProcEdit子程序中,返回的原窗口过程地址被保存到lpOldProcEdit变量中:
invoke GetDlgItem,hWnd,IDC_HEX invoke SetWindowLong,eax,GWL_WNDPROC,addr _ProcEdit mov lpOldProcEdit,eax
这样,当Windows向IDC_HEX编辑框发送消息时,_ProcEdit子程序就会收到消息,在_ProcEdit中我们仅处理感兴趣的WM_CHAR消息,程序在数据段中定义了一个允许输入的字符表,表中包括0~9、大小写的A~F以及退格键(如果不允许输入退格键的话将无法修正输入错误)。然后在处理WM_CHAR消息时使用scasb指令进行查表,如果字符在表中,则将WM_CHAR消息通过CallWindowProc函数转发给原来的窗口过程,如果不在表中则直接返回,相当于丢弃了这个按键动作。代码如下:
.const szAllowedChar db '0123456789ABCDEFabcdef',08h .code ... mov eax,wParam ;WM_CHAR消息中的一段代码 mov edi,offset szAllowedChar mov ecx,sizeof szAllowedChar repnz scasb .if ZERO? .if al > '9' and al,not 20h .endif invoke CallWindowProc,lpOldProcEdit,hWnd,uMsg,eax,lParam ret .endif
在转发之前,程序还对字符进行判断,如果字符是小写的a~f的话(表中的字符中大于“9”的肯定是字母),则通过and al,not 20h语句将字母转换成大写,因为大写字母的ASCII码从41h开始,小写字母从61h开始,这样的计算方法对大写字母没有影响,对小写的则刚好能够转换成大写的。程序将其他所有的消息原封不动地转发给原来的窗口过程,这样才能让编辑控件的窗口过程为我们完成控件的其他功能。
转发消息使用了CallWindowProc函数,这个函数仅起到将参数入栈和调用指定地址的作用,对于下面的语句我们完全可以用自己调用lpOldProcEdit的方法来代替它:
invoke CallWindowProc,lpOldProcEdit,hWnd,uMsg,eax,lParam
下面的代码就可以完成同样的功能:
push lParam push eax push uMsg push hWnd call lpO ldProcEdit
程序中的其他代码应该算是相当简单的,_DecToHex子程序是10进制到16进制的转换子程序,子程序中用GetDlgItemInt函数读入编辑框中的10进制数值,并用wsprintf转换成16进制数值的字符串并显示到IDC_HEX编辑框中;_HexToDec是16进制到10进制的转换子程序,由于并没有现成的转换函数,所以在子程序中顺序读入字符并每次通过乘以16来进行计算。
程序中还有一个技巧。由于使用SetDlgItemText设置编辑框文本的时候,编辑框会发送WM_COMMAND消息,由于一收到某个WM_COMMAND消息就进行转换计算,并再次使用SetDlgItemText函数将计算结果显示在另一个编辑框中,这样就会进入发送WM_COMMAND消息的死循环中。为此程序中定义了一个dwOption变量,当正在处理某个WM_COMMAND消息的时候,将这个变量设置为1来防止重入,这样就能够防止死循环的发生,代码如下:
.elseif eax == WM_COMMAND mov eax,wParam .if ! dwOption mov dwOption,TRUE .if ax == IDC_HEX invoke _HexToDec .elseif ax == IDC_DEC invoke _DecToHex .endif mov dwOption,FALSE .endif
对控件窗口进行子类化,影响的仅是被操作的窗口,并不会影响基于这种控件的其他窗口,因为SetWindowLong函数操作的对象仅是单个窗口而不是窗口类。所以要对多个控件窗口进行同样的子类化就必须对每个窗口都进行子类化操作。