分享
 
 
 

创建有个性的对话框之ATL/WTL篇

王朝other·作者佚名  2006-07-07
窄屏简体版  字體: |||超大  

前记

这几个嵌入类其实很早之间就完成了,2003年的时候我在CodeProject上发布了这些代码,不过当时使用了紫色作为按钮的边框,导致几个无聊的LY在哪里争吵关于Gays的问题,呵呵,看来不仅语言要接轨,颜色的认识也要和西方接轨哟。现在刚好趁这个机会整理一下代码,写篇文章纪念之。

本文的目的是使用ATL/WTL做一个与“创建有个性的对话框之MFC篇”的个性对话框一样的对话框。ATL/WTL一套模板库,创建ATL/WTL应用程序不可避免的要用到C++的模板与多继承方面的知识,在看本文之前希望您对它们有所了解。本文结尾可以下载文中介绍的例子代码,编译这些代码需要安装WTL库,对于WTL的更详细的信息请查看oRbIt翻译的“WTL for MFC Programmers”系列文章,具体位置在:http://blog.csdn.net/orbit

ATL和WTL一起构建了一个轻量级的应用程序框架,ATL在设计时接口定义和实现是严格区分开的,这在窗口类的设计中是最明显的,这一点类似于COM,COM的接口定义和实现是完全分开的(或者可能有多个实现)。ATL有一个专门为窗口设计的接口,可以做全部的窗口操作,这就是CWindow。它实际上就是对HWND操作的包装类,对几乎所有以HWND句柄为第一个参数的窗口API的进行了封装,例如:SetWindowText() 和 DestroyWindow()。在ATL类中对窗口过程的实现是CWindowImpl。CWindowImpl 含有所有窗口实现代码,例如:窗口类的注册,窗口的子类化,消息映射以及基本的WindowProc()函数,可以看出这与MFC的设计有很大的不同,MFC将所有的代码都放在一个CWnd类中。

由于ATL和MFC都是应用于Windows平台的库,所以他们都能够响应和处理系统发送的窗口消息,只是ATL和MFC对消息的分派方式不同,从而造成编写代码方面的差异。这些差异并不是不可逾越,ATL也定义了一些与MFC类似的宏来处理和分派消息,每个ATL的窗口类都用一个消息映射表或者称其为消息映射链,将消息处理函数与特定的消息关联起来,这和MFC的做法是类似的。少量的不同之处在于消息响应函数的参数,MFC对Windows的消息参数,也就是wParam和lParam进行了内部解释,传递给消息响应函数的参数比较友好,ATL的消息响应函数则是原原本本的将wParam和lParam传递给了消息响应函数,对Windows的消息不太熟悉的程序员可能会很迷惑。如果你对C++的模板机制比较了解,并且愿意不停的查阅MSDN,那就很容易将MFC的窗口类“翻译”成ATL/WTL的窗口类,比如本文用到的CWzButtonImpl类就是从本文的“MFC姊妹篇”中例子代码的CSMButton类翻译过来的。

在开始用ATL/WTL创建个性对话框之前,还要介绍一下ATL中常用的嵌入类(Mix-in class)。ATL的另一个显著不同之处就是任何一个C++类都可以响应消息,而MFC只是将消息响应任务分给了CWnd类和CCmdTarget类,外加几个有PreTranslateMessage()方法的类。ATL的这种特性允许我们编写所谓的“嵌入类”,为我们的窗口添加特性只需将该类添加到继承列表中就行了,就这么简单!一个基本的带有消息映射链的类通常是模板类,将派生类的类名作为模板的参数,这样它就可以访问派生类中的成员,比如m_hWnd(CWindow类中的HWND成员)。让我们来看一个嵌入类的例子,这个嵌入类通过响应WM_ERASEBKGND消息来画窗口的背景:

template <class T, COLORREF t_crBrushColor>

class CPaintBkgnd : public CMessageMap

{

public:

CPaintBkgnd() { m_hbrBkgnd = CreateSolidBrush(t_crBrushColor); }

~CPaintBkgnd() { DeleteObject ( m_hbrBkgnd ); }

BEGIN_MSG_MAP(CPaintBkgnd)

MESSAGE_HANDLER(WM_ERASEBKGND, OnEraseBkgnd)

END_MSG_MAP()

LRESULT OnEraseBkgnd(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)

{

T* pT = static_cast<T*>(this);

HDC dc = (HDC) wParam;

RECT rcClient;

pT->GetClientRect ( &rcClient );

FillRect ( dc, &rcClient, m_hbrBkgnd );

return 1; // we painted the background

}

protected:

HBRUSH m_hbrBkgnd;

};

让我们来研究一下这个新类。首先,CPaintBkgnd有两个模板参数:使用CPaintBkgnd的派生类的名字和用来画窗口背景的颜色。(t_ 前缀通常用来作为模板类的模板参数的前缀)CPaintBkgnd也是从CMessageMap派生的,这并不是必须的,因为所有需要响应消息的类只需使用BEGIN_MSG_MAP宏就足够了,所以你可能看到其他的一些嵌入类的例子代码,它们并不是从该基类派生的。

构造函数和析构函数都相当简单,只是创建和销毁Windows画刷,这个画刷由参数t_crBrushColor决定颜色。接着是消息映射链,它响应WM_ERASEBKGND消息,最后由响应函数OnEraseBkgnd()用构造函数创建的画刷填充窗口的背景。要在我们的窗口中使用这个嵌入类需要做两件事:首先,将它加入到继承列表:

class CMyWindow : public CWindowImpl<CMyWindow, CWindow, CFrameWinTraits>,

public CPaintBkgnd<CMyWindow, RGB(0,0,255)>

其次,需要CMyWindow将消息传递给CPaintBkgnd,就是将其链入到消息映射链,在CMyWindow的消息映射链中加入CHAIN_MSG_MAP宏:

typedef CPaintBkgnd<CMyWindow, RGB(0,0,255)> CPaintBkgndBase;

BEGIN_MSG_MAP(CMyWindow)

MESSAGE_HANDLER(WM_CLOSE, OnClose)

MESSAGE_HANDLER(WM_DESTROY, OnDestroy)

COMMAND_HANDLER(IDC_ABOUT, OnAbout)

CHAIN_MSG_MAP(CPaintBkgndBase)

END_MSG_MAP()

任何CMyWindow没有处理的消息都被传递给CPaintBkgnd。应该注意的是WM_CLOSE,WM_DESTROY和IDC_ABOUT消息将不会传递,因为这些消息一旦被处理消息映射链的查找就会中止。你可以在继承列表中使用多个嵌入类,每一个嵌入类使用一个CHAIN_MSG_MAP宏,这样消息映射链就会将消息传递给它。这与MFC不同,MFC的CWnd派生类只能有一个基类,MFC自动将消息传递给基类。

了解了ATL消息处理方式和嵌入类的知识之后,就可以开始创建ATL/WTL的彩色对话框了。在“创建有个性的对话框之MFC篇”中提到了一种最简单的改变对话框背景颜色的方法,就是调用CWinApp::SetDialogBkColor()函数,很不幸,ATL/WTL没有提供这个方法,至少ATL 7.1和WTL 7.1是这样的。不过没关系,我们可以向MFC那样通过处理一些特殊的窗口绘制消息来实现彩色的对话框,而且ATL/WTL的方式更加灵活,稍后就会看到。

首先是修改对话框的背景,这个只需要处理WM_ERASEBKGND消息就行了,在ATL的窗口中也可以这样做,不过ATL可以使用更加灵活的嵌入类,前面提到的CPaintBkgnd就是这样的嵌入类。使用嵌入类将WM_ERASEBKGND消息的处理函数封装到CPaintBkgnd类中,避免了CMainDlg代码的过度臃肿,同时也使得创建同样背景的对话框更加用以,不需要拷贝OnEraseBkgnd中的代码到新对话框类,只需将CPaintBkgnd加入到新类的集成列表中就行了:

class CMainDlg : public CDialogImpl<CMainDlg>,

public CPaintBkgnd<CMainDlg, RGB(0,0,255)>

这样CMainDlg就拥有了蓝色的背景,改变CPaintBkgnd的模板参数t_crBrushColor可以定制对话框不同的颜色,下面的代码使得对话框拥有了淡绿色的背景:

class CMainDlg : public CDialogImpl<CMainDlg>,

public CPaintBkgnd<CMainDlg, RGB(215,241,215)>

图1.使用了CPaintBkgnd的对话框

和在MFC中处理WM_ERASEBKGND消息一样,控件的背景颜色还没有改变,下面本文就介绍在ATL/WTL框架下如何响应WM_CTLCOLORXXX消息。

MFC将所有的WM_CTLCOLORXXX系列消息都映射为WM_CTLCOLOR消息,只是通过OnCtlColor()函数的参数区分他们,不过WM_CTLCOLORDLG消息并没有被映射为WM_CTLCOLOR,因为MFC对为WM_CTLCOLORDLG消息做了其他处理,如果你愿意追踪一下CWinApp::SetDialogBkColor()的代码你就会看到MFC是如何处理WM_CTLCOLORDLG消息的。也正式因为MFC对WM_CTLCOLORDLG“另有打算”,使得本文的“MFC姊妹篇”中的例子为了改变对话框的背景色不得不处理了WM_ERASEBKGND消息。但是使用ATL/WTL则不需要额外处理WM_ERASEBKGND消息,因为处理WM_CTLCOLORDLG通知消息更简单。ATL/WTL没有对WM_CTLCOLORXXX系列消息统一处理,不过如果集中处理这些消息能够简化代码编写,提高与MFC代码的互换性,我们也可以将其映射到一个消息处理函数中,事实上下面将要介绍的CCtrlColor就是这样做的。

下面就是使用了CCtrlColor之后的效果图,这里没有继续使用CPaintBkgnd,因为CCtrlColor中处理了WM_CTLCOLORDLG通知消息。

图2.使用了CCtrlColor的对话框

在对话框中使用位图背景也很简单,只要照着CPaintBkgnd样子写一个画位图的类就行了,以下是一个简单的例子:

template <class T, UINT uBitmapID>

class CBitmapBkgnd : public CMessageMap

{

public:

CBitmapBkgnd() { m_Bitmap.LoadBitmap(uBitmapID); }

~CBitmapBkgnd() { m_Bitmap.DeleteObject(); }

BEGIN_MSG_MAP(CPaintBkgnd)

MESSAGE_HANDLER(WM_ERASEBKGND, OnEraseBkgnd)

END_MSG_MAP()

LRESULT OnEraseBkgnd(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)

{

T* pT = static_cast<T*>(this);

HDC hDC = (HDC) wParam;

RECT rcClient;

pT->GetClientRect ( &rcClient );

BITMAP bm;

m_Bitmap.GetBitmap(&bm);

CDC memDC;

memDC.CreateCompatibleDC(hDC);

HBITMAP hOldBmp = memDC.SelectBitmap(m_Bitmap);

StretchBlt(hDC,0,0,rcClient.right - rcClient.left,rcClient.bottom - rcClient.top,memDC,0,0,bm.bmWidth,bm.bmHeight,SRCCOPY);

memDC.SelectBitmap(hOldBmp);

memDC.DeleteDC();

return 1; // we painted the background

}

protected:

CBitmap m_Bitmap;

};

和CPaintBkgnd相比,CBitmapBkgnd只是修改了第二个模板参数和OnEraseBkgnd()函数的代码,由于使用位图作为对话框的背景,对话框中的控件在处理WM_CTLCOLORXXX系列消息时应该返回一个空画刷而不是默认的背景颜色画刷,也就是需要一个定制的处理WM_CTLCOLORXXX系列消息的嵌入类,本文用了CCtrlHollowColor与CBitmapBkgnd配合使用,但是CCtrlHollowColor过于简单,它只能在本文的例子中使用,如果对话框中有更多的控件就需要修改CCtrlHollowColor类的代码。下面是使用位图背景的效果:

图3.使用位图背景的对话框

现在该考虑按钮的问题了,和本文的MFC姊妹篇一样,本文设计的WTL例子也使用了一个自画按钮类。前文已经提过,它其实就是从CSMButton“移植”过来的,你肯定觉得还是有些不一样,那是因为CWzButtonImpl使用了ATL的特性,那就是使用WTL的嵌入类:COwnerDraw。在MFC的按钮类CSMButton中,WM_DRAWITEM消息被MFC做了内部映射,最终的按钮绘制映工作交给了虚函数DrawItem(),这是利用了C++的多态机制。ATL/WTL不使用虚函数,多继承和嵌入类是它拥有比MFC更加灵活的方式避开了使用虚函数,这也就使得ATL/WTL的窗口对象比MFC的窗口对象占用更少的内存。只要将COwnerDraw类加入窗口类的集成列表,就可以原封不动的将CSMButton::DrawItem()搬到CWzButtonImpl中,以下是CWzButtonImpl类的完整声明,可以看到它是个窗口类,具有CButton的接口:

class CWzButtonImpl : public CWindowImpl<CWzButtonImpl, CButton>,public COwnerDraw<CWzButtonImpl>

CWzButtonImpl的使用方法可在ATL中与MFC有较大的差异,它只能通过SubclassWindow()这个方法与控件关联起来,不过有了WTL之后就和MFC一样了,因为ATL也实现了类似MFC的DDE/DDV。

如果一个对话框中有很多个按钮,一个一个添加关联也是一件很麻烦的事情,尤其是当你需要从对话框中删除某个按钮的时候。在这里我要介绍一个有用的辅助类:CButtonHelp。CButtonHelp是一个模板类,它的作用就是在对话框初始化的时候枚举对话框中所有的控件,找出其中的按钮控件并用模板参数指定的按钮类子类化(Subclassing)这个控件。以下是CButtonHelp类的声明:

template <class T, class t_ButtonClass>

class CButtonHelp

第一个模板参数是窗口类,第二个模板参数就是按钮类,通过替换模板参数t_ButtonClass可以在不同的对话框中使用不同风格的按钮,下面的例子就是本文使用的例子代码中主对话框的声明,他使用CWzButtonImpl来子类化按钮控件:

class CMainDlg : public CDialogImpl<CMainDlg>, public CCtrlColor<CMainDlg>,

public CButtonHelp<CMainDlg,CWzButtonImpl>

假设还有一个自画按钮类CBitmapButtonImpl,下面的声明将使CBmpDlg对话框拥有了另一种风格的自画按钮:

class CBmpDlg : public CDialogImpl<CBmpDlg>, public CCtrlColor<CBmpDlg>,

public CButtonHelp<CBmpDlg,CBitmapButtonImpl>

CButtonHelp类有三个主要的函数,一个是SubclassAllButtons(),这个函数遍历对话框所有的子控件,子类化其中具有pushbutton风格的按钮,SubclassButton()子类化指定的按钮,UnSubclassButton()则是SubclassButton()的逆过程。下面就是使用了CButtonHelp和CWzButtonImpl后的效果:

图4.使用自画按钮后的对话框

至此,本文已经使用ATL/WTL实现了MFC例子中的全部功能,通过对比可以看到这两个FrameWork各有各的优点,对同一个问题的解决方案也是一样的,只是编码的方式不同。本文使用的例子代码可以在文后的链接中下载。

http://blog.csdn.net/images/blog_csdn_net/orbit/demo2.zip[url=http://blog.csdn.net/images/blog_csdn_net/orbit/demo.zip][/url]

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有