摘 要:本文首先简要论述了Windows的控件子类化技术,然后通过实际的软件开发项目中的例子,详细介绍了控件子类化技术如何扩展通用控件的功能,从而满足了软件的特殊需求。
关键词:通用控件;子类化;Visual C++
引 言
尽管VC提供了丰富的通用Windows控件,如树状控件、列表控件、按钮控件、滑杆条等。每个控件的功能非常强大,提供的事件函数也比较多,但是在某些特殊的需求情况下,VC提供的某个通用控件的事件函数仍不能满足实际软件应用的特殊要求。例如,笔者正在开发的邮件客户端管理软件项目中就遇见了许多这样的问题。在该软件项目的某个对话框中有一个带有CHECKBOX核对框的列表控件,该控件用来显示从二进制文件中读取的数据项。要求奇/偶单击该核对框CHECKBOX时,使得该选择项Item为有效/无效状态,同时选择该Item;当单击该选择项Item时选择该Item,但是不能改变Item的有效/无效状态。另外,还要求该列表控件能够处理键盘事件如按Delelete键可以删除该Item,空格键改变Item的有效/无效状态等。由于VC没有提供列表控件的相应事件函数,因此必须借助于其他方法或者其他成熟的官方控件才能实现上述特殊功能。而成熟的官方控件又比较少并且不一定提供相应的事件处理函数,因此一个较好的方法就是利用控件子类化技术随心所欲地实现上述各种复杂的功能。
一、控件子类化技术
所谓控件子类化方法就是从已有的控件类派生出子类,然后利用该子类具有的消息函数进行windows事件处理的一种方法。其原理就是通过继承的方式,用我们自己的消息处理函数来替代控件原有的、标准的处理函数。当然我们自己的控件处理函数只是关心那些特定的消息,而其它消息,仍发给原来的控件函数处理。子类化的方法有三种:
l 一种是对某个控件类进行子类化,将影响其后创建的所有该控件类的控件实例;
l 一种是对已经建立的控件实例进行子类化,子类化的结果是只影响这一个控件实例;
l 一种是在某个控件类的基础上新注册一个子类化的控件类,不会影响原有控件类。在 Windows 中,这种技术又称为超类化。
本文主要介绍经常使用且比较简单的第一种方法。通过该方法派生出来的子类控件在窗口容器中和普通的控件没有区别,仍然是原有的通用控件,但是在编码实现(消息处理)上,该子类控件因增加了其通用控件的消息处理函数,而增强了通用控件的功能。下面就举上述实际的软件项目的例子来说明具体的功能实现。
二、控件子类化技术示例说明
(1)在资源对话框放置列表控件
启动VC++ 6.0,创建一个基于对话框的工程文件TestApp,打开资源对话框,拖放一个列表控件,设置该控件的Style属性的View为:Report。
(2)添加列表控件变量
选择该列表控件,激活MFC ClassWizard窗口,通过Member Variables添加CListCtrl控件类变量m_ListCtrl。
(3)列表控件子类化
从CListCtrl类派生出一个子类为CSubListCtrl,然后把该子类的头文件和实现文件加入到工程来。
(4)列表控件子类化变量声明
打开该Cdialog类的头文件,找到如下代码:
//{{AFX_DATA(xxxxxxxxx)
ClistCtrl m_ListCtrl;
//}}AFX_DATA
把ClistCtrl m_ListCtrl;代码注释掉。在//}}AFX_DATA后添加如下一行代码:
CSubListCtrl m_ListCtrl;
当然也可以直接声明该子类化控件的变量。另外,注意要包含相应的子类化控件的头文件。
(5)通用列表控件和子类化控件的事件函数比较
打开资源对话框,选择CListCtrl控件,激活MFC ClassWizard窗口,在Messages列表框中显示出该通用列表控件的所有的消息处理函数。
在ClassView中选择CSubListCtrl,激活MFC ClassWizard窗口,在ClassName下拉框中选择CSubListCtrl类,显示该子类的消息函数。
从Messages列表框中可以看出,该CSubListCtrl子类增加了大量的通用列表控件的消息处理函数,因而增强了通用控件的事件处理功能,从而满足了软件的特殊需求。
(5)添加单击鼠标左键的事件处理函数
激活MFC ClassWizard窗口,添加OnLButtonDown()函数。编写自己的消息处理函数,代码如下:
void CSubListCtrl::OnLButtonDown(UINT nFlags, CPoint point)
{
//判断是否单击某个Item
int nClickItemIndex = HitTest(point, &nFlags);
if (nClickItemIndex == -1)
{
return;
}
//判断是否单击Label左边的CheckBox框
CRect rect;
GetItemRect(nClickItemIndex, rect, LVIR_LABEL);
BOOL bHitIcon = FALSE;
if (point.x < rect.left)
{
//单击了Label左边的CheckBox框
bHitIcon = TRUE;
}
// 改变Item的Check状态(有效/无效/禁止)
ChangeItemCheckState(nClickItemIndex, bHitIcon);
CListCtrl::OnLButtonDown(nFlags, point);
}
由于篇幅原因,未给出ChangeItemCheckState()函数的代码实现,读者可根据自己的情况来实现相应代码。
(6)添加键盘事件处理函数
同上述方法,添加键盘事件OnKeydown(),编写自己的消息处理函数,代码如下:
void CSubListCtrl::OnKeydown(NMHDR* pNMHDR, LRESULT* pResult)
{
LV_KEYDOWN* pLVKeyDow = (LV_KEYDOWN*)pNMHDR;
CSetMailRulePage *pWnd = (CSetMailRulePage*)GetParent();
int nClickItemIndex = 0;
POSITION pos;
switch (pLVKeyDow->wVKey)
{
//按“Delete”键要执行的函数
case VK_DELETE:
……
break;
//按“空格”键要执行的函数
case VK_SPACE:
……
break;
default :
break;
}
*pResult = 0;
}
(7)访问父窗口
在子类CSubListCtrl代码中,如果想访问父窗口中的其他控件,可以通过下列代码:
CSubListCtrl *pParentWnd = (CSubListCtrl*) GetParent();
获取父窗口的指针,根据pParentWnd指针就可以访问父窗口中其他控件。
类似地通过上述方法,添加相应的消息处理函数,编写不同的事件处理代码,就可以方便地实现应用程序的特殊功能。
三、结 语
本文介绍了Windows控件子类化技术并举例说明具体的实现。这种方法,不但比较简单易行,而且提高了软件开发效率,实现了软件项目的特殊功能要求。因此该方法在实际的软件项目开发中应用较为广泛。
参考文献
[1]David J. Kruglinski. Visual C++ 技术内幕(第四版). 潘爱民,王国印译. 北京:清华大学出版社,1999 [2]Jon Bates, Tim Tompkins. 实用Visual C++ 6.0 教程. 何健辉,董方鹏等译. 北京:清华大学出版社,2000
|