MFC窗口子类化
某些时候我们需要改变窗口的默认行为,就需要改变窗口过程(子类化窗口),总结了以下三种方法,欢迎讨论。
1)最简单的方法,可以通过传递GWL_WNDPROC调用SetWindowLong来改变窗口过程,可是这个函数还需要窗口句柄作为参数,而句柄只有在窗口创建成功后才合法,所以这种做法只能当不用改变默认WM_CREATE行为(以及一些窗口创建过程中的动作)的时候才适用。
2)窗口过程是在窗口类注册时确定的,要改变窗口函数就需要注册一个新类(参考DuiLib中的Edit空控件实现):
首先调用系统API获得窗口类的详细信息,结果放在lpwcx指向的内存区中。
BOOL WINAPI GetClassInfoEx( _In_opt_ HINSTANCE hinst, _In_ LPCTSTR lpszClass, _Out_ LPWNDCLASSEX lpwcx );这样能就改变WNDCLASSE结构中的lpfnWndProc指向的窗口过程了,最好先把旧值保存下来,以便在自定义窗口过程中调用来做默认动作。然后就可以给这个WNDCLASS赋一个新类名,并注册类。用这个类创建的函数就默认调用我们自定义的函数作为窗口过程了。
最好可以像MFC一样将窗口处理函数写作类成员函数,在窗口过程中调用与此窗口相关联类的成员函数。
我们需要一个转换,在窗口过程中可以将一个HWND转换成我们定义的封装类。
可以定义一个Map做成HWND到封装类的映射,这个映射对象应该是全局唯一的,所以可以作为类静态成员变量。
调用Create(或CreateEx)窗口创建时,向最后一个参数lParam传递对象自身的指针this,这个参数会直接传递到WM_NCCREATE(和WM_CREATE)结构成员中((CREATESTRUCT*)lParam)->lpCreateParams。然后将封装类指针和窗口句柄的映射关系存储起来。最后记得在WM_DESTORY(或WM_NCDESTORY)从映射对象中删除即将销毁的窗口句柄映射。
大功告成了,我们可以在自定义的窗口过程中用窗口句柄查询映射对象得到封装类指针,可以肆无忌惮的调用类成员函数。也可以调用原来保存的旧窗口过程来做默认动作。
3)我们也可以用一种更柔和的方法,使用系统钩子布置好陷阱,然后静等窗口自投罗网。
Refer To:
http://hi.baidu.com/invisiable/item/d38f25fe54a78a1ba62988da
http://www.vckbase.com/index.php/wv/430
关键就在怎么部这道网。
首先在窗口创建之前(当然可以在应用程序对象 InitInstance过程中)安置一个系统钩子,钩子类型可以设为WH_CALLWNDPROC,这个钩子会在系统调用窗口过程之前调用,还可以设置线程ID为目标窗口创建线程。
钩子函数里我们主要工作就是筛选目标窗口(因为目标线程的所有窗口过程调用都会提前通知我们定义的钩子过程)。
筛选方式可以参考一下步骤:
1.CWPSTRUCT* pStruct = (CWPSTRUCT*)lParam;
2.检查是否是WM_CREATE消息。
因为我们的钩子过程是在调用窗口过程之前调用的,所以我们检测到目标窗口的CREATE消息,接着就替换目标窗口的窗口过程,钩子过程完成后系统再去调用窗口过程时已经是我们偷梁换柱之后的啦、
3.使用GetClassName得到窗口的窗口类,检查时候是目标窗口类。
4.如果上面两条都满足就可以肯定本地调用的窗口就是我们的目标窗口,但是这里需要查看我们是否已经处理过这一个窗口了,一个简单的方法就是使用SetProp给处理过的窗口做一个标记。所以这一部可以检查GetProp得到属性值。
5.如果走到这里我们可以确定需要对这个窗口子类化了。首先GetWindowLongPtr获取窗口默认窗口过程,把返回值用SetProp设置为窗口的属性,这样在自定义窗口过程中可以GetProp获得默认窗口过程做默认动作。所以上一步的检测是这样的:
if (::GetProp(pStruct->hwnd, DEFINE_PROP_NAME) != NULL)
break;调用SetWindowLongPtr设置新的窗口过程。
6.和第二种方法中的理由一样,最好能把窗口句柄和包装类对象最为一个映射保存起来,这样就可以在窗口过程中调用封装类成员函数了。