任务失败:构建精简界面类库

探讨了在不借助框架的情况下,使用Win32API自定义绘制控件的挑战及解决方案,重点介绍了自绘按钮控件的具体实现过程。
提起用VC++开发Windows程序,就一定少不了提起MFC,MFC已经足够应付大多的情况了,但坦白说我不喜欢MFC,因为其臃肿和不够高效,所以很多时候我都是直接用Win32 API来编写我的应用程序,显而易见,这个是很有难度的,主要的难度也许你已经知道了,就是界面的编写。
 
现在我们来回顾一下,如何用MFC写界面,要创建一个button,大致可以这样:
[csharp]  view plain copy
  1. CButton btn;  
  2. btn.Create(TEXT(“capital”), BS_PUSHBUTTON, &rect, pwndParent, BUTTON_ID);  
之后还可以调用CButton的各种方法来实现对该button的操作。如果觉得这个button不够漂亮,可以把该button设为BS_OWNERDRAW,然后重载成员函数DrawItem,自己手动对它进行绘制,如果还是觉得不够好,可以自行添加对WM_MOUSEMOVE等消息的处理函数。这些操作,和Win32 API几乎是一一对应的,所以我们可以说MFC是对Win32 API的轻度封装。
 
现在,我的任务就是写一个超级轻便的界面类库,使用上类似MFC,而又不需要借助一套框架,我先把这套想象中的类库称为FlatControls,顾名思义,它主要是对一些现有的控件进行包装重绘,把它们变成Flat风格。
 
要改变控件外观,我们立即想到前面提到的OWNERDRAW风格,不少控件都支持这种风格,设置了这种风格后,如果控件需要绘制,就会向其父窗口发送一个WM_DRAWITEM的消息,这个消息的参数中包含了这个要绘制的控件的许多有用信息,包括控件的窗口句柄和DC(Device Context),以及此控件的一些状态,这些状态用来指导控件绘制,是非常必要的,比如一个button,有是否按下状态,有Enable/Disable状态,有Active/非Active状态,等等。
 
OK,功能既然明确,我就开始动手了,我的第一个控件类名称叫CFlatButton,(下文皆以这个为例)同MFC的CButton那样,我给它安排了一个OnDrawItem()的方法,用来绘制这个按钮,可问题马上来了,谁来调用OnDrawItem()这个方法?显而易见,是此控件的Owner(父窗口),但父窗口凭什么收到了WM_DRAWITEM就来调这个方法?我这个时候不想关心这个button的父窗口是谁啊,如果我一定要这个button的父窗口收到WM_DRAWITEM之后来调OnDrawItem,那我就得关心一下这个父窗口了,起码我要在这个父窗口的消息处理函数里加上类似这么一段:
[csharp]  view plain copy
  1. //Parent window’s procedure  
  2. switch (message)   
  3. {  
  4.     case WM_DRAWITEM:  
  5.     {  
  6.         //...  
  7.         //pCtrl is the pointer to the CFlatButton, you got it by some ways.  
  8.         pCtrl->OnDrawItem(wParam, lParam);  
  9.     }  
  10.     break;  
  11. }  
(图1)
这就意味着我需要改变父窗口,这样就违背了我设计这套精简界面类库的初衷。顺便告诉你,MFC就是这么干的,因此你试图把CButton这个class从MFC中分离出来的话,你是不会成功的,MFC的那些支持owner draw的控件类都从CWnd派生,在CWnd的message map中,我们可以找到ON_WM_DRAWITEM这项,看看就明白了。那不通过owner draw,我们能不能完全手工自己把控件写出来?我一开始认为这是没问题的,可一做下去,发现完全又不是这么回事,好,下面我来谈谈。
 
大家都知道,每个窗口都有一个自己的消息处理函数,这个处理函数是在RegisterClass中指定的,也就是说在CreateWindow之前,就已经指定了,但指定归指定,我们还是有办法修改它的,SetClassLong和SetWindowLong就是方法,我们可以通过这两个函数之一,来将Window的消息处理重定向到我们的函数中去,比如这样:
[csharp]  view plain copy
  1. class CFlatControl  
  2. {  
  3.     //...  
  4. virtual LRESULT DefWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)=0;  
  5. }  
  6.    
  7. class CFlatButton : public CFlatControl  
  8. {  
  9. public:  
  10. //...  
  11.     WNDPROC DefWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);  
  12. //...  
  13. protected:  
  14.     HWND m_hwnd;  
  15.     WNDPROC m_wpOld;  
  16. }  
  17.    
  18. CFlatButton::Create(/* Arguments */)  
  19. {  
  20.     m_hwnd = CreateWindow(/* Arguments */);  
  21.     m_wpOld = (WNDPROC)SetWindowLong(m_hwnd, GWL_WNDPROC, (LONG)DefWindowProc);  
  22. }  
 
这段伪码的意思是说,在flat button被创建的时候,就改变它的默认处理函数为CFlatButton的成员函数DefWindowProc,这样我们只需要充分实现DefWindowProc即可实现真正意义上独立于任何框架的界面类。是不是这样呢?
 
也许你马上说,上面的代码是有问题的,将一个类的成员函数转变为LONG,是不会成功的,哈,果然厉害,确实如此,好吧,现在我们就来改改这个问题,把这个成员函数换成一个全局的函数,叫DefFlatControlProc(HWND hwnd, UINT, WPARAM, LPARAM),利用参数hwnd,透过一张哈希表,查找到对应的CFlatControl object,调用这个object的方法DefWindowProc,这样就OK了,代码如下:
[csharp]  view plain copy
  1. HASHTABLE *g_phashHWNDtoCFlat; //Initialize this hashtable by a global function as AfxWinInit()  
  2.    
  3. class CFlatControl  
  4. {  
  5.     //...  
  6. virtual LRESULT DefWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)=0;  
  7. }  
  8.    
  9. class CFlatButton : public CFlatControl  
  10. {  
  11. public:  
  12. //...  
  13.     LRESULT DefWindowProc(HWND, UINT, WPARAM, LPARAM);  
  14. //...  
  15. protected:  
  16.     HWND m_hwnd;  
  17.     WNDPROC m_wpOld;  
  18. }  
  19.    
  20. WNDPROC DefFlatControlProc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)  
  21. {  
  22.     CFlatControl *p = (CFlatControl *) g_hashHWNDtoCFlat->Find((DWORD)hwnd);  
  23.     if(p!=NULL)  
  24.         return p-> DefWindowProc(hwnd, uMsg, wParam, lParam);  
  25.     return 0;  
  26. }  
  27.    
  28. CFlatButton::Create(/* Arguments */)  
  29. {  
  30.     m_hwnd = CreateWindow(/* Arguments */);  
  31.     m_wpOld = (WNDPROC)SetWindowLong(m_hwnd, GWL_WNDPROC, (LONG)DefFlatControlProc);  
  32.     g_hashHWNDtoCFlat->Add((DWORD)m_hwnd, (DWORD)this);  
  33. }  
  34.    
  35. LRESULT CFlatButton::DefWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)   
  36. {  
  37.     HWND hwndUnderPoint;  
  38.     POINT point;  
  39.     switch(uMsg)  
  40.     {  
  41.     WM_MOUSEMOVE:  
  42.         // Do what you want to do here.  
  43.         break;  
  44.     default:  
  45.         break;  
  46.     }  
  47.     return ::CallWindowProc(m_wpOld, hwnd, uMsg, wParam, lParam);  
  48. }  
 
(图2)
终于把伪码敲完了,其实真实的代码跟这个差不多的,这么一改,是不是貌似复杂了很多?多了一个初始化函数(我在伪码里没写出来,其实MFC也有类似这么个函数的,你试用向导创建一个MFC console程序看看,就知道了),多了一张全局哈希表,多了一个全局窗口消息处理函数。这么一来,我这个精简界面类库还算不算精简?我写这个不容易啊,你就给点面子说“算”吧……那么,这回总算OK了吧,接下去就是完善那个CFlatButton::DefWindowProc了吧……
 
嗯,理论上是这么说的,可真正到了自己写这个函数的时候,才知道什么叫难度,没有owner draw结构的那几个状态的指导,想完美地画好一个button是非常非常困难的,究竟有多难,我建议大家自己动手写写,一个button不只是一个方框那么简单,你还要考虑鼠标按下的情况,鼠标移动,鼠标按下移开,鼠标按下移开再进入……加上按钮是否激活,是否有效,是否默认等等情况,我写了两天后,快崩溃了,于是宣告任务失败,就成了这文章的标题。
 
现在我终于明白,为什么这么多界面类库都是MFC的扩展,而不是完全另开炉灶,自成一家。还有我也明白了,企图建立一个完全独立的微型界面类库(不用任何初始化就能独立使用的那种),是不可能的,要不就是有重大功能缺失,比如不能使用owner draw。
 
我对刚刚说的“不可能”突然感到有些后悔了,其实还是有可能的,但难度根本不在我的能力范围内,那就是完全自己创建一套控件,不使用Windows自带的控件,但这个无需我多解释,你也知道有多难,对个人来说……
 
我对天大喊:NEXT TIME! NEXT TIME!
The sky replied: NEXT TIME! NEXT TIME!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值