提起用
VC++开发Windows程序,就一定少不了提起MFC,MFC已经足够应付大多的情况了,但坦白说我不喜欢MFC,因为其臃肿和不够高效,所以很多时候我都是直接用Win32 API来编写我的应用程序,显而易见,这个是很有难度的,主要的难度也许你已经知道了,就是界面的编写。
现在我们来回顾一下,如何用
MFC写界面,要创建一个button,大致可以这样:
CButton btn;
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,那我就得关心一下这个父窗口了,起码我要在这个父窗口的消息处理函数里加上类似这么一段:
//Parent window’s procedure
switch (message)
{
case WM_DRAWITEM:
{
//...
//pCtrl is the pointer to the CFlatButton, you got it by some ways.
pCtrl->OnDrawItem(wParam, lParam);
}
break;
}
![](https://p-blog.csdn.net/images/p_blog_csdn_net/guogangj/misson_failed_1.gif)
(图
1)
这就意味着我需要改变父窗口,这样就违背了我设计这套精简界面类库的初衷。顺便告诉你,
MFC就是这么干的,因此你试图把CButton这个class从MFC中分离出来的话,你是不会成功的,MFC的那些支持owner draw的控件类都从CWnd派生,在CWnd的message map中,我们可以找到ON_WM_DRAWITEM这项,看看就明白了。那不通过owner draw,我们能不能完全手工自己把控件写出来?我一开始认为这是没问题的,可一做下去,发现完全又不是这么回事,好,下面我来谈谈。
大家都知道,每个窗口都有一个自己的消息处理函数,这个处理函数是在
RegisterClass中指定的,也就是说在CreateWindow之前,就已经指定了,但指定归指定,我们还是有办法修改它的,SetClassLong和SetWindowLong就是方法,我们可以通过这两个函数之一,来将Window的消息处理重定向到我们的函数中去,比如这样:
class CFlatControl
{
//...
virtual LRESULT DefWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)=0;
}
class CFlatButton : public CFlatControl
{
public:
//...
WNDPROC DefWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
//...
protected:
HWND m_hwnd;
WNDPROC m_wpOld;
}
CFlatButton::Create(/* Arguments */)
{
m_hwnd = CreateWindow(/* Arguments */);
m_wpOld = (WNDPROC)SetWindowLong(m_hwnd, GWL_WNDPROC, (LONG)DefWindowProc);
}
这段伪码的意思是说,在
flat button被创建的时候,就改变它的默认处理函数为CFlatButton的成员函数DefWindowProc,这样我们只需要充分实现DefWindowProc即可实现真正意义上独立于任何框架的界面类。是不是这样呢?
也许你马上说,上面的代码是有问题的,将一个类的成员函数转变为
LONG,是不会成功的,哈,果然厉害,确实如此,好吧,现在我们就来改改这个问题,把这个成员函数换成一个全局的函数,叫DefFlatControlProc(HWND hwnd, UINT, WPARAM, LPARAM),利用参数hwnd,透过一张哈希表,查找到对应的CFlatControl object,调用这个object的方法DefWindowProc,这样就OK了,代码如下:
HASHTABLE *g_phashHWNDtoCFlat; //Initialize this hashtable by a global function as AfxWinInit()
class CFlatControl
{
//...
virtual LRESULT DefWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)=0;
}
class CFlatButton : public CFlatControl
{
public:
//...
LRESULT DefWindowProc(HWND, UINT, WPARAM, LPARAM);
//...
protected:
HWND m_hwnd;
WNDPROC m_wpOld;
}
WNDPROC DefFlatControlProc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
CFlatControl *p = (CFlatControl *) g_hashHWNDtoCFlat->Find((DWORD)hwnd);
if(p!=NULL)
return p-> DefWindowProc(hwnd, uMsg, wParam, lParam);
return 0;
}
CFlatButton::Create(/* Arguments */)
{
m_hwnd = CreateWindow(/* Arguments */);
m_wpOld = (WNDPROC)SetWindowLong(m_hwnd, GWL_WNDPROC, (LONG)DefFlatControlProc);
g_hashHWNDtoCFlat->Add((DWORD)m_hwnd, (DWORD)this);
}
LRESULT CFlatButton::DefWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
HWND hwndUnderPoint;
POINT point;
switch(uMsg)
{
WM_MOUSEMOVE:
// Do what you want to do here.
break;
default:
break;
}
return ::CallWindowProc(m_wpOld, hwnd, uMsg, wParam, lParam);
}
![](https://p-blog.csdn.net/images/p_blog_csdn_net/guogangj/misson_failed_2.gif)
(图
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!