关键字:子类化窗口,窗口过程.
关键函数:SetWindowLong,SetWindowLongPtr,CallWindowProc.
我们可以用SetWindowLong函数子类化一个窗口,使我们自己的窗口过程处理该窗口的大部分事件.不过有几点需要注意:
1.SetWindowLong只能用于Win32程序中,而SetWindowLongPtr可以用于64位操作系统.
2.SetWindowLong函数包含自commctl32.dll中,头文件是comctrl.h.
3.commctl32.dll在5.8版本(或6.0版本)之后增加了一些新的API,SetWindowSubclass函数可以替代SetWindowLong函数并且SetWindowSubclass有着比SetWindowLong更优越的方面.其一是可以通过uIdSubclass设置不同的子类ID.其二是可以通过dwRefData设置每个子类自己的数据.这样,不同的子类可以共享相同的窗口过程但却有着不同的子类ID和自己的数据.
4.个人认为BCB6.0是不支持SetWindowSubclass函数的,虽然可以在system32/commctl32.dll(XPSP3)中找到相关的导出函数并且在comctrl.h中也有该函数的定义.不过在VC9.0中是可以调用该函数的.这可能是因为编译器(我用的是BCB6.0)中对应的commctl32.lib中没有该函数.原因可能是BCB6.0中的commctl32.lib在commctl32.dll之前就已经编译好的.
5.MSDN中说的很详细,需要加上下面的代码.
#ifdef STRICT
WNDPROC OldEditProc;
#else
FARPROC OldEditProc;
#endif
同样的.
#ifdef STRICT
OldEditProc = (WNDPROC)SetWindowLong(edt1->Handle,GWL_WNDPROC,(LONG)NewEditProc);
#else
OldEditProc = (FARPROC)SetWindowLong(edt1->Handle,GWL_WNDPROC,(LONG)NewEditProc);
#endif
下面贴一下自己写的Demo.
Form.cpp
//--------------------------------------------------------------------------- #include <vcl.h> #pragma hdrstop #include "Form.h" //--------------------------------------------------------------------------- #pragma package(smart_init) #pragma resource "*.dfm" Tfrm1 *frm1; LRESULT CALLBACK Tfrm1::NewEditProc(HWND hWnd,UINT Msg,WPARAM wParam,LPARAM lParam) { switch(Msg) { case WM_CHAR: if((wParam<'0'||wParam>'9') && wParam!=VK_BACK) { MessageBeep(MB_OK); return 0; } default: return CallWindowProcA(frm1->OldEditProc,hWnd,Msg,wParam,lParam); } } //--------------------------------------------------------------------------- __fastcall Tfrm1::Tfrm1(TComponent* Owner) : TForm(Owner) { } //--------------------------------------------------------------------------- void __fastcall Tfrm1::FormCreate(TObject *Sender) { #ifdef STRICT OldEditProc = (WNDPROC)SetWindowLong(edt1->Handle,GWL_WNDPROC,(LONG)NewEditProc); #else OldEditProc = (FARPROC)SetWindowLong(edt1->Handle,GWL_WNDPROC,(LONG)NewEditProc); #endif } //---------------------------------------------------------------------------
Form.h
//--------------------------------------------------------------------------- #ifndef FormH #define FormH //--------------------------------------------------------------------------- #include <Classes.hpp> #include <Controls.hpp> #include <StdCtrls.hpp> #include <Forms.hpp> //--------------------------------------------------------------------------- class Tfrm1 : public TForm { __published: // IDE-managed Components TEdit *edt1; void __fastcall FormCreate(TObject *Sender); private: // User declarations #ifdef STRICT WNDPROC OldEditProc; #else FARPROC OldEditProc; #endif static LRESULT CALLBACK NewEditProc(HWND hWnd,UINT Msg,WPARAM wParam,LPARAM lParam); public: // User declarations __fastcall Tfrm1(TComponent* Owner); }; //--------------------------------------------------------------------------- extern PACKAGE Tfrm1 *frm1; //--------------------------------------------------------------------------- #endif
这个小例子是为了控制Edit中输入的字符只能是0-9.
值得补充的一点是,可以通过再次调用SetWindowLongPtr函数将窗口过程替换回原来的默认函数.
(WNDPROC)SetWindowLongPtr(hWnd,GWL_WNDPROC,(LONG)OldEditProc);
关于Subclassing 更详细的说明在MSDN上可以找到.
下面是从别的网页上摘下来的:
子类化(subclass)控件的新方法
注:本文原文(英文)出自微软MSDN网站,由袁晓辉翻译并发布在http://www.farproc.com/,供广大编程爱好者交流、学习只用。译者保留译文的一切权利,禁止将本译文用于商业用途。你可以转载该文章,但请保持其完整性并注明来自http://www.farproc.com/。
如果一个控件拥有我们想要的特性,但是我们想给它添加一些新的特性,可以通过子类化(subclass)这个控件来实现。一个子类除了拥有父类的一切特性外还拥有我们给它添加的任何新特性。
这篇文章讲讨论任何实现子类化,并包含如下话题:
* ComCtl32.dll version 6之前的控件子类化方法
* 使用ComCtl32.dll version 6 实现控件子类
ComCtl32.dll version 6之前的控件子类
在ComCtl32.dll 6.0 发布以前,我们也有办法来创建子类(译者注:使用SetWindowLong,SetWindowLongPtr API),但是这种方法存在一些缺点。
子类化控件
创建一个新的控件最好是以Windows通用控件为基础进行扩充以使它满足特定的需要。要扩展一个控件,可以创建一个控件然后用一个新的窗口过程(window procedure)替换它原来的窗口过程,新的窗口程序截获控件的消息,要么处理该消息,要么传递给原来的窗口过程进行默认处理。使用SetWindowLong 或 SetWindowLongPtr 函数可以替换 WNDPROC。下面的代码演示了任何替换 WNDPROC。
OldWndProc = (WNDPROC)SetWindowLongPtr (hButton,
GWLP_WNDPROC, (LONG_PTR)NewWndProc);
存储自定义数据
你可能需要为一个窗口存储一些自定义数据,新的窗口过程可以根据自定义数据来决定任何绘制控件或向哪个窗口发送一些特定的消息,还有,比如,你可能需要存储一个代表该控件的C++窗口类指针。下面的代码演示任何使用SetProp 来在一个窗口内存储一个字符串。
SetProp (hwnd, TEXT("MyData"), (HANDLE)pMyData);
旧的子类化方法的缺陷
下面的清单显示了用前面提到的那种方法来子类化控件时的一些缺陷
* 窗口过程只能被替换一次。
* 一个子类创建后很难清除。
* 给一个窗口管理私有数据效率比较低。
* 在传递消息给子类链中的下一个函数时你不能直接调用它,而是必须用过CallWindowProc 函数来实现。
使用ComCtl32.dll version 6 进行子类化
Windows XP 带的ComCtl32.dll version 6 提供了4个可以让创建子类化更简单,并且可以消除前面提到的缺陷的函数。这些新的函数封装了对多组参考数据(multiple sets of reference data)的管理操作,使得开发者能将精力集中到具体的程序特性而不是对子类的管理上。这些新的函数为:
* SetWindowSubclass
* GetWindowSubclass
* RemoveWindowSubclass
* DefSubclassProc
下面是对这些函数的描述。
SetWindowSubclass
这个函数用来子类化一个窗口。每个子类可以用p pfnSubclass 和 uIdSubclass (SetWindowSubclass的参数)唯一标识。多个子类可以共享同一个子类过程,而用标识(ID)来区分。改变参考数据可以提供再一次调用SetWindowSubclass 来实现。一个重要的优点是每一个子类实例可以拥有自己的参考数据。
子类过程的声明和传统的窗口过程有点细微的差别,它多了两个参数:子类ID和参考数据。参看下面这个函数声明的最后两个参数:
LRESULT CALLBACK MyWndProc (HWND hWnd, UINT msg,
WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass,
DWORD_PTR dwRefData);
每次新的窗口过程收到一个消息时,它同时得到了一个子类ID和参考数据。
注意:所有作为参数传递给该函数的字符串均为Unicode,不管有没有定义Unicode编译选项。
GetWindowSubclass
该函数取回一个子类的信息。比如你可以用GetWindowSubclass 来访问参考数据。
RemoveWindowSubclass
该函数移除一个子类. RemoveWindowSubclass 和 SetWindowSubclass 联合使用可以动态添加和删除子类.
DefSubclassProc
这个函数调用子类链中的下一个处理者。这个函数可以自行取得正确的ID和参考数据并传递给下一个窗口过程。