MFC-- 子类化控件

首先讲讲什么是子类化,其实子类化很好理解,和以前一样,仍然从win32 sdk方法开始,在这里也可以补充一下,我在一些群里的见到有些人关于MFC的说法,说直接就学MFC就可以了,没必要学win32,有的人把MFC说的很简单似的,其实不然,由于MFC对底层的隐藏和其复杂的框架,其实很多时候,我们学起来是很吃力的,而且,很多人照着一些编程书说的照着做,改来改去,最后做出来了,但是不知道其中的原理,当需要自己设计程序的时候,不知无从下手。从我自己学习windows桌面程序开发以来,发现不管是NET Framework,MFC,ATL,MFC ACTIVEX,MFC automation等等,其实要真正理解和学习,都要从win32开始,因为它们的底层就是它们,只有知道了他们,才能对MFC进行灵活的应用,在遇到错误的时候,可以分析程序,找到问题所在。也许你不会用到win32自己去编写,但是对于你理解是很很有帮助的。好了,言归正传。

先来看看在win32中,程序在窗口过程中处理各种行为,如用户的操作了消息,或是系统产生的消息,都在窗口过程中进行消息处理。那么子类化其实就是在消息到达窗口过程之前,再插入一个过程处理。原来我们只有一个窗口过程,有关这个窗口的消息,都在这个窗口过程中进行处理。但是现在我们在消息达到这个过程之前,进行一些处理,再将消息送到原来的窗口过程。在win32中,设置这个插入的窗口过程,是使用函数SetWindowLong。函数原型为:

LONG WINAPI SetWindowLong(
  _In_  HWND hWnd,
  _In_  int nIndex,
  _In_  LONG dwNewLong
);

我们知道在win32创建窗口(参见本博客“MFC-序幕”)过程中,第一步是声明一个WNDCLASS窗口类,然后填充成员,这样就设置了我们要创建的窗口的一些属性,如菜单,光标,程序图标等等,其中有一个成员就是设置窗口过程,完成之后,我们用RegisterClass函数注册这个窗口类(也可以用别的函数,还有一些MFC函数,但原理一样),之后我们就可以用这个窗口类创建窗口,那么用这个窗口类创建的窗口的消息,都会到达窗口类中设置的窗口过程函数中。而一个窗口是用句柄类标识的,因此在这里,SetWindowLong的第一个参数是窗口句柄,说明要在它的原来注册的窗口过程之前,要插入一个窗口过程函数,第二个参数是一个索引值,这个函数本来是用来改变窗口的注册窗口类中的属性的,如样式,实例句柄等等,索引值就是用来说明要修改的什么属性,其中,修改这个窗口指向的窗口过程,而这个新指向的窗口过程其中一个最重要的就是对不处理的消息传递给原来的窗口过程函数,第三个参数就是新值。成功之后,当消息来的时候,就会先到我们插入的这个新的窗口过程,然后再到原来的窗口过程。如下:


    if (uMsg == WM_GETDLGCODE) 
        return DLGC_WANTALLKEYS; 
 
    return CallWindowProc(OrigProc, hwnd, uMsg, wParam, lParam); 

这里就将不处理的消息传递给了原来的窗口过程。


那为什么要进行子类化呢,其实是为了在原来窗口过程处理之前,我们可以做一些预处理,或是拦截一些消息,不让传给原始窗口过程,或是有些消息,必须要子类化的,才可以处理的,如对话框上的控件,要处理WM_LBUTTONDOWN,WM_SETCURSOR等等标准的窗口消息,这些消息,如果我们不用子类化处理,直接传递给对话框,那么对话框就会进行处理之后,我们就只能处理控件的通知消息,如click等消息。

在MFC中在使用的窗口过程函数加switch/case语句来处理消息,而是使用窗口类加消息映射函数来处理,其实道理是一样。也就是它们对消息处理方式上的不同而已。在文章“MFC--消息”中说明了MFC的消息,也是结合win32来说明的,有兴趣的可以看看。下面以一个例子来说明MFC中的子类化。

首先我建立一个基于对话框的程序,然后在对话框上放了6个编辑框,ID分别是IDC_EDIT1到IDC_EDIT6,然后放了两个按钮,ID分别是IDC_BUTTON1和IDC_BUTTON2。如下图:


对于编辑框,有时候,我们希望当输入完成之后,我们按enter键之后,就完成一个编辑框的输入,而自动转到下一个编辑框,就不用使用鼠标或是tab键来移动编辑框的输入焦点,但是编辑框中对回车键的响应要么是换行(多行样式)或是不响应(单行样式),现在我设置的这个是单行输入,要实现enter键的时候,自动把焦点移动到下一个编辑框。下面两个按钮,对于我们画在对话框上的按钮,默认的鼠标指针形状是箭头指针,现在我要实现当把鼠标移动到“手形指针”按钮上的时候,变成手形指针。要实现这些,就要通过子类化来处理响应的消息来实现。

上面已经描述了,MFC中使用的窗口类和消息响应来处理消息的,现在我要将编辑框和按钮的消息到达对话框消息处理之前,对编辑框和按钮的消息进行处理,因为控件仍然窗口,也有标准的窗口消息,如WM_MOUSEMOVE,WM_LBUTTONDOWN等等。因此,先添加两个MFC类,CMyEdit和CMyButton,它们分别继承于CEdit和CButton,添加之后如下:


在主对话框头文件中包含两个头文件:

#include "MyEdit.h"
#include "MyButton.h"

在主对话框中声明对应的变量:

在主对话框的OnInitDialog中,设置子类化:


这样,当在控件上所有消息,会首先经过刚才我们添加的窗口中,然后才会到对话框中。现在我们先实现编辑框的输入焦点通过enter键来移动。

当一个窗口获取焦点的时候,会收到消息WM_SETFOCUS,当一个窗口失去焦点的时候,会收到WM_KILLFOCUS。因此我处理这两个消息,在CMyEdit类中设置一个布尔值,判断该窗口是否有焦点,当获取焦点的时候,就设置为TRUE,当失去焦点的时候为FALSE。如下:


接着再响应WM_KEYUP消息,来过滤enter键按下的情况。并且我设置了一个自定义消息,向主对话框发送消息,让它来通过每个编辑框对象中的布尔值来判断当前那个编辑框有焦点,然后使用SetFocus函数将焦点到下一个编辑框。如下:


当收到enter弹起的消息之后,向主对话框发送一个自定义消息WM_SETCONTROLFOCUS,接着对这个自定义消息进行处理,如下:


CheckFocus是我声明和定义的一个函数,在当中处理输入焦点的转换。这样就完成了这样一个功能。

下面实现将一个按钮上讲鼠标指针变换成手形,那么首先要说明指针。鼠标指针默认情况是使用在注册窗口类的时候,所设置的指针类型,当然,在对话框中,不是这样,对话框是预定义的一种窗口类。如果要在不改变窗口类属性的情况下修改鼠标指针的形状,包括普通窗口和对话框,控件等类型的窗口,可以处理WM_SETCURSOR消息,如果我们不处理这个消息,那么系统会使用默认的鼠标指针,如果要使用我们自己的指针,那么就可以在这个消息中,设置我们自己的指针,完成之后,还要设置返回值为非0值,告知系统我们提供了我们自己的指针,不用系统为我们提供指针了。好了,知道这个原理之后,我们在CMyButton中添加消息处理WM_SETCURSOR消息,作如下处理:


这样就实现了将这个子类化的button上当鼠标移到的时候,变成手形的鼠标指针。上面通过这个例子,说明子类化的应用,但是最重要的是理解子类化的原理,那么在以后自己程序设计的过程中,知道应该如何利用子类化,而子类化的应用这里的例子只是为了说明它的原理,而其应用完全不止这些。

而CWnd的SubclassWindow这个函数呢,要注意的一点,那个窗口在使用子类化之前没有绑定到某个MFC对象之上,也就是说一个HWND还没有被MFC对象使用Attach将其于一个MFC对象绑定。另外,通过 UnsubclassWindow函数可以解除对某个窗口的子类化。对于MFC 对话框控件,还有一个函数,也可以方便的使用子类化,SubclassDlgItem,这个函数的第一个参数是控件的ID,第二个参数是其父窗口对象指针。这个效果和SubclassWindow一样,只是参数不一样,使用SubclassWindow要自己通过win32的GetDlgItem获取控件的窗口句柄。而SubclassDlgItem不用。

更多信息,参考msdn。

推荐编程者访问网站:http://www.panshy.com 获取更多知识。

本节代码:http://download.csdn.net/detail/xinzhiyounizhiyouni/6791417


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章