子类化控件,子类化窗口
亚纲
如果你曾经在Windows环境下写过一个程序,有时候你会发现有一个现成的窗口,几乎包含了你需要的所有功能,但又不太一样。如果你需要一个具有过滤特殊字符功能的编辑控件。当然,最直接的方法是用代码实现,但这确实是一件费时费力的事情,可以用子类化来做这种事情。
子类化允许你接管已经被子类化的窗口,给你绝对的控制权。例如,您需要一个只接受十六进制数字的文本编辑框。如果你使用一个简单的编辑控件,当用户输入除十六进制以外的字符时,你将一无所知,无事可做。也就是说,当用户在文本框中输入字符串‘zb q *’时,如果除了拒绝接受整个字符串之外,几乎什么也做不了,至少显得特别不专业。重要的是,你需要有输入检测的能力,也就是说,你应该能够在每次用户把一个字符输入编辑框的时候检测到它。
现在来解释一下实现细节:当用户在文本框中输入字符时,Windows会向编辑控件的窗口函数发送WM_CHAR消息。这个窗口函数本身是寄生在Windows中的,所以不能直接修改。但是我们可以重定向这个消息,并将其发送到我们自己的窗口处理程序。如果自定义窗口想要处理此消息,它可以处理它。如果没有,它可以将这个消息转发给它原来的窗口处理程序。通过这种方式,自定义窗口处理程序将其自身插入到窗口系统和编辑控件之间。
解释
看下面的过程:
子类化之前
编辑控件的窗口处理程序。
子类化后
Windows -用户定义的窗口处理程序-编辑控件的窗口处理程序。
请注意,子类化不限于控件。您可以创建任何窗口的子类。现在我们必须关注如何创建窗口的子类。让我们考虑一下Windows是如何知道编辑控件的窗口处理程序放在哪里的。WNDCLASSEX结构成员LpfnWndProc指出了窗口函数地址。如果可以用自己的窗口函数的地址替换这个成员变量,那么Windows就不会把消息发送给自定义窗口函数了!我们通过调用函数SetWindowLong来实现这个任务,它的原型是:
SetWindowLong(HWNDhWnd,intnIndex,LONGdwNewLong);
HWnd=要子类化的窗口的句柄。
NIndex=函数子函数索引
设置窗口的扩展样式。
设置一个新的窗口样式
GWL_WNDPROC设置新的窗口处理地址。
Gw _ hint设置一个新的应用程序句柄。
GWL ID设置一个新的窗口标识符。
GWL _用户数据为用户设置一个与该窗口相关的32位数据。
DwNewLong=用于更新的数据
我们的工作相对简单:
写一个窗口函数来处理发送到编辑控件的消息。用参数GWL_WNDPROC调用SetWindowLong函数(#add通常用GetWindowLongPtr获得)。如果调用成功,返回值是一个与调用函数相关的32位整数。在我们的程序中,返回值是原始窗口函数的地址。我们希望保存这个值供以后使用。记住:有一些消息是我们不处理的,需要调度到原来的窗口函数中进行处理,所以使用了另一个函数CallWindowProc。该函数的原型是:
LRESULTCallWindowProc(WNDPROClpPrevWndFunc,HWNDhWnd,UINTMsg,WPARAMwParam,LPARAMlParam);
LpPrevWndFunc=窗口原始函数的地址。(保存在#add上面的留在这里使用)
剩下的四个参数是发送给自定义函数的参数。只需将它们直接传递给CallWindowProc函数。
附:
子类化的功能
子类化技术最大的特点就是可以拦截Windows消息。一旦用户定义的窗口函数截获了发送给原始窗口函数的消息,被截获的消息可以被如下处理:
把它传递给原来的窗口函数。这是大多数消息应该采取的措施,因为子类通常只对原始的窗口特性做一些改变。
拦截消息,防止它被发送到原来的窗口函数。
修改消息,然后发送到原窗口函数。
Windows SDK提供了一些设计好的窗口类,如EDIT、LISTBOX、TREEVIEW等。通过截取这些通用窗口类的消息,用户程序可以给它们增加新的特性,改善它们的外观,扩展它们的功能。
子类化的优势主要体现在以下两个方面:第一,它不需要创建一个新的窗口类,不需要知道一个窗口的窗口进程。这在原来的窗口函数是别人写的,创建过程不可见的情况下非常有用;其次,子类化很容易实现,因为所有要做的工作就是写一个窗口函数。
在VC中实现窗口子类化
上面说的子类化是来自于Windows自带窗口函数的概念,实际上属于SDK编程的范畴,在MFC中是不一样的。下面将描述在这两种情况下实现窗口子类化的方法。
VC中基于SDK编程的窗口子类化:
你要做的就是写一个窗口函数。
基本步骤如下:
(1)正常创建原始窗口,获取窗口的句柄。
(2)调用GetWindowLong获取原窗口函数OldWndProc。
(3)调用SetWindowLong设置新窗口函数NewWndProc。
新窗口函数的代码如下:
LRESULT NewWndProc(HWND hWnd,UINT消息,WPARAM wParam,LPARAM lParam)
{
if(message==WM_IcareIt)
{
//截取你感兴趣的新闻,做一些处理,达到改变特征的目的。
}
//如果需要,可以调用原来的窗口函数,让子类化的窗口仍然具有原来的特性。
返回CallWndowProc(OldWndProc,hWnd,message,wParam,lParam);
}
值得注意的是,调用旧窗口函数时,不能直接使用OldWndProc(…),必须用CallWndProc函数调用,否则会出现堆栈错误。
MFC编程中的窗口子类化:
MFC窗口实际上是子类化的窗口。所有MFC窗口共享相同的窗口函数。这个窗口函数根据窗口句柄,查找这个窗口对应的CWnd派生类实例,然后通过消息映射这个窗口类的消息处理函数。由于上述原因,在MFC中创建窗口的子类更容易,因为你的任务是编写一个新的MFC窗口类,而不用编写窗口函数。
如果我们现在有一个带有编辑控件的对话框,我们只想在这个控件中接受非数字字符输入。我们可以截取WM_CHAR消息,并忽略其处理函数中的任何数字输入。MFC编程中窗口子类化的具体步骤将在下一节通过一个简单的例子来说明。
MFC窗口子类化的应用实例
MFC为程序员提供了许多功能丰富的窗口类。如果这些通用的窗口类可以被子类化,会给程序员带来很多方便。这里有一个例子来说明MFC编程中的子类化是多么简单。这个例子完成了上面提到的在编辑控件中只接受非数字字符输入的功能。实现这个子类的基本步骤和相关代码如下:
(1)使用AppWziard创建基于对话框的程序子类化。
(2)修改MFC提供的标准对话框中的控件,删除MFC提供的静态文本控件,添加一个自己的编辑控件,并将新控件的ID设置为IDC_EDIT。合理安排对话框上控件的位置,使程序界面布局合理美观。
(3)用ClassWizard从CEdit类派生出一个新的窗口类,新窗口的窗口类叫做CNoNumEdit。拦截CNoNumEdit类的WM_CHAR消息,完成OnChar函数中忽略任何数字输入的处理。实现代码如下:
void CNoNumEdit:on char(UINT nChar,UINT nRepCnt,UINT nFlags)
{
TCHAR ch=nChar;
if(ch=_T(0) ch=_T(9 ))
{
AfxMessageBox((请不要输入数字!),MB _ OK);
//当输入数字字符时,它们将被忽略并显示警告消息。
返回;
}
CEdit:OnChar(nChar,nRepCnt,nFlags);//当输入为非数字字符时,调用原始处理函数
}
(4)将变量CNoNumEdited添加到对话框窗口类CSubClassingDlg的定义中。在CSubClassingDlg:OnInitDialog()函数中,调用CWnd类的成员函数SubClassWindow对其进行子类化。
未经修改。subclass window(GetDlgItem(IDC _ EDIT)-m _ hWnd);
(5)打电话通知。对话框窗口类的OnDestroy中的UnSubClassWindow()子类化窗口类的子类。
现在这个程序可以编译和执行了。当用户输入数字字符时,输入将被忽略,并将显示一条警告消息。
在Windows编程中,恰当地使用窗口子类化技术可以很容易地达到改变窗口特性的目的。当然,子类化也有其局限性。其实子类化的概念是关于一个已经创建好的窗口的,所以修改窗口函数是在窗口创建之后进行的,窗口创建过程中的消息是无法捕捉的,所以无法处理。另外,有些窗口的特性与窗口类本身的属性有关。例如,如果一个窗口类没有CS_DBLCLKS属性,就不可能对这些窗口进行子类化来处理WM_LBUTTONDBLCLK消息。超分类技术可以消除超分类的上述局限性。
超类需要注册一个新的窗日类,从而达到改变窗日类各种特性的目的。超分类的简单过程是获取一个已有的窗日类的特征,然后改变这些特征,最后再注册一个窗日类。
具体步骤如下:
定义一个WNDCLASSEX类型的变量。因为需要注册一个新的窗口日类,所以有必要定义这个变量。
调用GetClasslnfoEx函数获取想要超类的窗口类的信息。
改变窗口类的基本特征。显然,必须更改窗口类名和模块句柄hlnstance。注意,如果需要改变window类的窗口函数,在改变窗口函数之前要保存原窗口函数,在新的窗口日函数中,要把不需要处理的消息转移到原窗口函数中,以保留原窗口类的一些特性。
使用修改后的WNDCLASSEX变量,调用RegisterClassEX函数重新注册一个新的窗口类。
创建这个新窗口日类的窗口日实例。
(1)用MFC应用程序向导创建一个新的MDl程序SuperClassingo。
(2)使用ClassWizard构建一个从CWnd类派生的新类CDblClkWnd。增加双击MDl客户窗口左键的处理功能:
(3)重新注册一个窗口类,超类。
BOOL CDblClkWnd:register newclass()
{
WNDCLASS wc
如果(!GetClassInfo(NULL, MDIClient ,wc))
返回FALSE
wc。
WC . lpsz class name= DBLCLCMDIClient ;修改名称
返回register class(WC);
}
在APP类的InitInstance函数之前创建主框架的代码之前,调用上面注册新窗口的类的代码。
如果(!CDblClkWnd:RegisterNewClass())
返回false
使用CreateWindowEx创建MDIClient窗口时,将原始窗口类MDI Client更改为DB_LCLCMDIClient
在主窗口中添加变量CDblClkWnd m_client,并在主窗口的OnCreate中添加MDIClient子类。在OnDestroy中分解子类。
pclient。suh classwindow(phWndMDlClient);
m _客户端。UnsubclassWindow();//反子类化(#从这里可以得出结论,没有超类化和子类化两个概念,都是子类化。超分类是某人提出的。不如说子类化有两种形式,有一个超分类就很混乱,庸人自扰。超分类,我还是要找到这个词的出处。)