[Win32]什么是控件
声明:
这个题目起的非常的大,写完了,有一点后悔了.毕竟我只写了不到两个月的Win32程序,对其认识还不是很全面.如有错误,请路过的各位神仙达人指出来,也算是对小弟的帮助.:-)
一. Windows的消息机制
Windows是消息驱动的(看"现代操作系统",里面管这个这种叫事件驱动),发生消息,响应消息,本身就是一个松散耦合的设计.
不管是什么窗口,Create之前都需要注册一下类别,都需要RegisterClass.这个Class的概念,和OO里面的Class的概念没有本质的区别,而CreateWindow需要制定ClassName,所创建出来的也就是这个Class的一个实例.
而对消息的响应,是在Class的成员WndProc里面完成.为啥放到这里?龙生龙,凤生凤,老鼠的孩子会打洞~~同一个Class对消息处理的形式是一样的,因为他是同一个Class.
再来看这个松散耦合结构的原理.
由于CreateWindow时需要指出ClassName,那么一个窗口便会有一个ClassName,一个WndProc,这在外面来是比较直观而且比较重要的.一个HWND会有一个WndProc,这是显而易见的.有了这点,主线程就不用操心了,他只需要从消息队列里面拿出消息,分发下去,然后执行.
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
这就是我们常见的消息循环.
因为MSG有一个成员HWND,而一个HWND有成员ClassName,进而能得知WndProc,那么DispatchMessage执行也就变得比较容易.
主线程只需要一个劲的GetMessage,DispatchMessage就行,拿到谁的消息,就到谁的WndProc哪里去执行.
static LRESULT ImageButtonProc(HWND,UNIT,WPARAM,LPARAM); WNDPROC procImageButton = NULL; WNDPROC procOld = NULL; procImageButton = ImageButtonProc; procOld = (WNDPROC)GetWindowLong(hWnd,GWL_WNDPROC); BOOL result = SetWindowLong(hWnd,GWL_WNDPROC,(LONG)procImageButton); ASSERT(result); static LRESULT ImageButtonProc(HWND hWnd,UNIT message,WPARAM wParam,LPARAM lParam) { LRESULT result = 0; switch(message) { case WM_ERASEBKGND: return TRUE; case WM_PAINT: //在这里画 return result; case WM_LBUTTONDOWN: SetCapture(hWnd); return result; case WM_LBUTTONUP: ReleaseCapture(); //这里就发生了Click事件 //当然这只是假设,还需要根据鼠标点击位置具体判断 return result; } result = CallWindowProc(procOld,hWnd,message,wParam,lParam); }
以上是我们写独立的控件的前键.
另外一个条件就是我们需要对窗口过程进行控制.因为Button点下去是要相应OnClick的,而Lable点击下去,基本上什么都不做.正是因为这种差异性,导致我们需要对WndProc进行控制.
Windows本身就考虑到这一点,所以他提供了RegisterClass,允许我们注册自己的Class,对WndProc进行一些个性化的处理.而Windows默认的控件,比如Static,比如Edit,对消息的处理,都是他
设定好的.
有了RegisterClass,那么我们就可以写很多控件,只需我们搞1个或者n个WndProc就可以.
OK,那是一种办法,可是我不想用那种方法.
Windows还为我们提供了另外一种机制.我们要做一个个性化的控件,无非就是要一个窗口句柄和他的消息处理么,只要我们能拿到这些就完事.
GetWindowLong/SetWindowLong就能帮我们做些事.
OK,来看怎么做.
二. 自己打造一个控件
其实这里,我之前都说过了,翻翻我的Blog就能找到.
不管你用什么办法或得到的窗口句柄hWnd,要想把它做成一个ImageButton,那么基本上只需要这来:
如果我们把上面的ImageButtonProc填写完整,那么,那个窗口hWnd就变成了一个ImageButton,不管他之前是ListView,还是Static.可以看到GetWindowLong/SetWindowLong给了我们很大的想象空间.
三. 考虑封装
在上面,已经完成了一个控件,这个这个控件还不是很容易使用,其根本原因是编程模型的问题.那玩意儿是纯过程的模型,跟我们平时见到的OO模型的类库感觉上不太一样.虽然我们也可以通过某些方式,使得那个东西好使起来,用着也挺顺手,那就需要用"一"的一些思想,以及依赖倒置的C实现,否则貌似还真有一点麻烦.本文暂时还不想讨论这个问题....
各个控件对于消息的处理是不同的,而只有控件自己才知道自己需要怎么的处理,这有可能还是一个动态的不同.而窗口过程显然是全局的,是静态的,他对消息的处理只能是静态.所以要想搞成类库的话,必须要解决这个问题!
这让我想起了MFC,MFC那种OO封装的方式,你可以在自己的类里面处理各种各样的消息,而不去管真正的消息循环.
现在还是要看看"一",Windows本身给我提供了很多想法,需要我们好好去想.DispatchMessage拿到消息,只是简单的根据hWnd,找到他的窗口过程,执行了起来.而,hWnd->WndProc,这本身就是一个映射的概念.他通过这个映射,解决了依赖问题.
我们也可以这么做,只是这个映射也是需要我们自己搞定的!
//这个只是大概的原型 //说明原理而已 class Control { public: HWND hWnd; Control() { //xxxxx //这里就是上面的,想办法搞一个hWnd,然后替换窗口过程 } virtual LRESULT WndProc(HWND,UINT,WPARAM,LPARAM); static void Register(HWND,Control*); protected: WNDPROC newProc; WNDPROC oldProc; }; //Register的实现 static map<HWND,Control*> controlCache; void Control::Register(HWND hWnd,Control* pControl) { ASSERT(hWnd && pControl); controlCache.insert(make_pair<HWND,Control*>(hWnd,pControl)); } LRESULT DefaultWndProc(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam) { map<HWND,Control*>::iterator i = controlCache.find(hWnd); ASSERT(i!=controlCache.end()); if(i!=controlCache.end()) { return (*i)->WndProc(hWnd,message,wParam,lParam); } return DefWindowProc(hWnd,message,wParam,lParam); }
代码的原型就是那个样子,默认的窗口过程只是分发消息,真正的处理在你自己的控件里面.
想法来源于一两个星期前用Win32做UI的时候,虽然最后放弃了OO封装(因为时间不够),但是我觉得简单的OO封装,可以方便构造出我们想要的控件.
因为Windows默认控件都比较丑陋,美化的话基本上只有画,而画的话,我用什么画不是画,为什么非要基于Win32默认控件提供的那种方式来自绘呢?我曾经使用过ListView,发觉那个控件不是ListView,是上帝控件,超级复杂,后来我放弃了那个控件,自己用Static画了一个,代码也不过200行,而且定制性还很强.(一个GridView也是类似的)
PS:
1. 有人管GetWindowLong/SetWindowLong叫子类化,想想也是,我那处理也本身就是继承的过程.
2. 后来跟群里面一个朋友聊,发现我所认识的其实很早就有了,MFC就是类似的,底层只负责分发,上层处理
3. 说白了,我对OO的认识是比较片面的,所以我才能写出来别人几十年之前实现的东西%>_<%
4. 因为我没看过模板(因为TC++PL里面没写...),二者公司不允许使用WTL,所以我基本上不了解ATL/WTL那一套机制,不知道他是怎么处理和分发消息的
这要是本文的一个缺陷,认识比较片面