-----------------------------------------------------------------------------------------------------------------
作者: colin<linzhenqun@gmail.com>
-----------------------------------------------------------------------------------------------------------------
如何封装窗口过程是所有UI库最核心的设计之一,像MFC/WTL/VCL都各有自己的实现代码,最终的目标却是一样的:将窗口过程转化为对象的方法,从而把面向过程的Windows转化为面向对象的方式。这里要介绍我自己想到两种实现方式,这些实现都很简单,但都可以达到同样的目的。
一、利用窗口的属性表
基本原理是将窗口句柄和窗口类实例绑定在一起,每个窗口都有自己的属性表,通过GetProp和SetProp等API操作之,属性表本质上就是哈希表,通过一个Key快速查找到Value,在这里Key就是窗口句柄,Value就是窗口类。
另外,我们需要一个自己的标准窗口过程,用来找到与句柄关联的窗口类,然后再调用窗口类的窗口过程。
先看看实现的代码:
#ifndef WndHandler_h__ #define WndHandler_h__ #include <crtdbg.h> #define _WINDOW_CLSATOM "wndatom" class WndHandler { public: WndHandler(): mDefWndProc(NULL), mHwnd(NULL) { } virtual ~WndHandler() { Unsubclass(); } bool Subclass(HWND hwnd) { Unsubclass(); _ASSERT(::IsWindow(hwnd)); _ASSERT(::GetPropA(hwnd, _WINDOW_CLSATOM) == NULL); mDefWndProc = (WNDPROC)::GetWindowLongW(hwnd, GWL_WNDPROC); if (mDefWndProc == WndHandler::StdWndProc) mDefWndProc = DefWindowProcW; ::SetWindowLongW(hwnd, GWL_WNDPROC, (long)StdWndProc); ::SetPropA(hwnd, _WINDOW_CLSATOM, (HANDLE)this); mHwnd = hwnd; return true; } void Unsubclass() { if (!::IsWindow(mHwnd) || !mDefWndProc) return; ::SetWindowLongW(mHwnd, (LONG)mDefWndProc, GWL_WNDPROC); ::RemovePropA(mHwnd, _WINDOW_CLSATOM); mDefWndProc = NULL; mHwnd = NULL; } protected: static LRESULT CALLBACK StdWndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { WndHandler* handler = (WndHandler*)GetPropA(hwnd, _WINDOW_CLSATOM); _ASSERT(handler); LRESULT ret = 0; BOOL done = handler->WndProc(hwnd, msg, wparam, lparam, ret); if (done) return ret; else return CallWindowProcW(handler->mDefWndProc, hwnd, msg, wparam, lparam); } virtual BOOL WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam, LRESULT& ret) { switch (msg) { case WM_NCDESTROY: { mHwnd = NULL; break; } } return FALSE; } protected: WNDPROC mDefWndProc; HWND mHwnd; }; #endif // WndHandler_h__
WndHandler提代SubClass和UnsubClass两个方法对窗口进行子类化和反子类化。
SubClass大概做法是先取到原来的窗口过程,然后调用自己的窗口过程(StdWndProc),最后通过SetProp将WndHandler的实例塞进窗口的属性表中。UnsubClass则是将原来的窗口过程恢复过去,同时通过RemoveProp将类实例从属性表中删除。
当子类化成功后,窗口过程变成了StdWndProc,在这里通过GetProp找到WndHandler实例,再调用它的WndProc方法,这样就完成了从窗口过程向对象方法的转换。
二、利用哈希表
上面的实现有一些缺点,一个是属性表的名字固定,如果这个窗口刚好有这个属性名,则会把这个属性给冲掉;另一个缺点是这字符串方式的,其中必然会有将字符串计算成哈希值的过程,这对效率有微小的影响。
其实我们完全可以用自己的哈希表来表现关联,下面是另一个实现的代码:
WndHandler.h: #ifndef WndHandler_h__ #define WndHandler_h__ #include <crtdbg.h> #include <algorithm> #include <hash_map> using namespace std; using namespace stdext; class WndHandler { public: WndHandler(): mDefWndProc(NULL), mHwnd(NULL){} virtual ~WndHandler() { Unsubclass();} bool Subclass(HWND hwnd); void Unsubclass(); protected: static LRESULT CALLBACK StdWndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam); virtual BOOL WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam, LRESULT& ret); protected: WNDPROC mDefWndProc; HWND mHwnd; }; #endif // WndHandler_h__
WndHandler.cpp:
#include "stdafx.h" #include "WndHandler.h" class wnd_hash { public: enum { bucket_size = 4, min_buckets = 8 }; wnd_hash() { } size_t operator()(const HWND& hwnd) const { return size_t(hwnd); } bool operator()(const HWND& hwnd1, const HWND& hwnd2) const { return hwnd1 != hwnd2; } }; typedef hash_map<HWND, WndHandler*, wnd_hash> WndHandlerMap; static WndHandlerMap sWndHandlerMap; bool WndHandler::Subclass( HWND hwnd ) { Unsubclass(); _ASSERT(::IsWindow(hwnd)); mDefWndProc = (WNDPROC)::GetWindowLongW(hwnd, GWL_WNDPROC); if (mDefWndProc == WndHandler::StdWndProc) mDefWndProc = DefWindowProcW; ::SetWindowLongW(hwnd, GWL_WNDPROC, (long)StdWndProc); sWndHandlerMap.insert(make_pair(hwnd, this)); mHwnd = hwnd; return true; } void WndHandler::Unsubclass() { sWndHandlerMap.erase(mHwnd); if (!::IsWindow(mHwnd) || !mDefWndProc) return; ::SetWindowLongW(mHwnd, (LONG)mDefWndProc, GWL_WNDPROC); mDefWndProc = NULL; mHwnd = NULL; } LRESULT CALLBACK WndHandler::StdWndProc( HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam ) { WndHandlerMap::iterator itr = sWndHandlerMap.find(hwnd); if (itr == sWndHandlerMap.end()) return 0; WndHandler* handler = itr->second; LRESULT ret = 0; BOOL done = handler->WndProc(hwnd, msg, wparam, lparam, ret); if (done) return ret; else return CallWindowProcW(handler->mDefWndProc, hwnd, msg, wparam, lparam); } BOOL WndHandler::WndProc( HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam, LRESULT& ret ) { switch (msg) { case WM_NCDESTROY: { mHwnd = NULL; break; } } return FALSE; }
注意上面的hash_map会因VS的版本不同而不同,这里用的是VS2003;代码与第一个版本基本一致,只不过将属性表转换为哈希表了。
1269

被折叠的 条评论
为什么被折叠?



