ATL窗口(2)

类别:VC语言 点击:0 评论:0 推荐:

WTL窗口

(ATL窗口 第2部分)                  翻译:孙凯

---------------------------------------------------------------

这篇文章由Andrew Whitechapel所写。

---------------------------------------------------------------

下载代码12K

绪论

在我的第一篇文章中,我讲解了ATL的窗口类。文章带来了一个问题,关于从MFC移往ATL时缺少前台GUI支持的问题。这个争论的结果,产生了Microsoft Windows Template Library(WTL)。我会带你通过十分简单的步骤创建一个基于WTL的框架-视图应用程序,让你自己写出从MFC转向ATL的感受。

WTL是ATL的扩展,也是由ATL小组开发,包含在Microsoft于2000年1月发布的开发平台SDK包中(也可以从Microsoft网站上下载),虽然Microsoft没有正式支持。WTL通过提供一个用于编写Win32应用程序和控制的轻量级的框架,一些特殊的视图,GDI对象和实用的类,来扩展了ATL窗口类。

WTL包由750KB的窗口类库头文件,三个例子和一个Visual Studio WTL AppWizard组成。当然,它还要依靠有1MB大小ATL文件。

要安装WTL,你要做以下的工作:

将WTL目录中的内容复制到你指定的位置。

将WTL\include目录加入到VC++的头文件目录(include directories)列表中。

复制文件appwiz\atlapp60.awx到VC++的定制应用程序向导(Custom App Wizard)目录中,%VCDIR%\Common\MSDev98\Template,%VCDIR%即是安装VC++时指定的目录。

WTL设计特性--附带地,相对于MFC的优势--包括:

模板化,因此有较小的代码量。例如,一个简单的“hello world”SDI应用程序,基于WTL的程序只有24KB,而MFC静态连接结果是440KB,MFC动态连接的结果是24KB+1MB。

无太多相关性,并且可以自由地和SDK代码直接混合。

不会强迫使用特定的应用程序模型,尤其相对于MFC的应用程序框架。

WTL类包括:

标准控制(编辑框,列表框,按钮等等)

公共控制(包括列表视图,树形视图,进度条,微调按钮)

IE控制(rebar,平面滚动条,日历等等)

命令条,菜单,和更新UI类

公共对话框

属性单和页类

框架窗口,MDI框架和子框架,分隔条,可滚动的窗口

设备环境(DC)和GDI对象类(笔、刷子、位图等)

打印机及其信息和设备模式类

实用工具类:包括CPoint, CRect, CSize, 和CString类

WTL AppWizard允许你生成SDI、MDI、多线程SDI和基于对话框的应用程序。多线程SDI应用程序就象IE或Windows Explorer(我的电脑),看起来象是启动了多个实例,实质上它们是同一进程的多个视图。这些视图可以是普通的基于CWindowImpl的窗口,或基于窗体、列表框、编辑框、列表视图、树形视图、丰富文本编辑框或HTML控制。你可以让你的应用程序拥有rebar、命令条(如同Windows CE)、工具条或状态条。你的应用程序可以包含ActiveX控制,甚至可以是一个COM服务器。

Hello WTL

在这个练习中,我们将创建一个基于WTL的简单的"Hello World"应用程序。

创建一个新的WTL AppWizard应用程序。取名HelloWorld。在WTL AppWizard的第一步对话框中,接受所有默认选项,单击下一步按钮。第二步对话框中,也是保留所有默认选项(包含工具条、rebar、命令条、状态条和视图窗口),单击完成按钮。现在就编译运行该程序。你会看到一个非常普通的Win32应用程序,它有标准的框架窗口和视图、菜单、工具条、状态条,以及关于对话框。File|Exit, View|Toolbar, View|Statusbar, 和Help|About菜单项/工具条按钮可以工作,尽管其它的不能。而且,菜单中的某些菜单项具有相应的工具条按钮图标:

现在来分析代码。首先,你应该注意到,在_tWinMain函数中有一个标准的ATL CComModule全局变量,它执行了初始化和终止函数。分析_tWinMain函数,你会发现它所做的其它工作仅仅是初始化公共控件(通过调用InitCommonControlsEx函数)和调用全局函数Run(处理消息循环)。Run函数创建主框架窗口和CMessageLoop对象,调用主框架窗口的ShowWindow函数,然后调用CMessageLoop::Run函数。CMessageLoop::Run函数实质上是轮流调用GetMessage和DispatchMessage函数。

然后,让我们看一看由AppWizard产生的CMainFrame类。它的所有的父类定义在WTL\ATLFrame.h或WTL\ATLApp.h中。其中主要的功能来自于CFrameWindowImpl类。

CUpdateUI通过UPDATE_UI_MAP宏被连接起来,并且最终到达了我们的派生类CMainFrame中的OnViewToolBar和OnViewStatusBar函数。它们做的操作是我们所期望的ShowWindow和SetCheck。

派生于CMessageFilter和CIdleHandler类意味着CMainFrame类必须实现一个消息过滤器。这个消息过滤器用来在分派消息之前清除某些不需处理的消息(例如,改变用户击键被处理的方式)。当消息队列中没有任何消息时,空闲处理函数被调用。

派生的视图类和CComandBarCtrl对象作为成员变量被嵌入到框架窗口类中。

 

class CMainFrame : public CFrameWindowImpl<CMainFrame>,

public CUpdateUI<CMainFrame>,

public CMessageFilter, //消息过滤

public CIdleHandler //空闲处理

{

public:

DECLARE_FRAME_WND_CLASS(NULL, IDR_MAINFRAME) //定义框架窗口类信息

CHelloWorldView m_view;

CCommandBarCtrl m_CmdBar;

BEGIN_MSG_MAP(CMainFrame) //消息映射

MESSAGE_HANDLER(WM_CREATE, OnCreate)

COMMAND_ID_HANDLER(ID_APP_EXIT, OnFileExit)

COMMAND_ID_HANDLER(ID_FILE_NEW, OnFileNew)

COMMAND_ID_HANDLER(ID_VIEW_TOOLBAR, OnViewToolBar)

COMMAND_ID_HANDLER(ID_VIEW_STATUS_BAR, OnViewStatusBar)

COMMAND_ID_HANDLER(ID_APP_ABOUT, OnAppAbout)

CHAIN_MSG_MAP(CUpdateUI<CMainFrame>)

CHAIN_MSG_MAP(CFrameWindowImpl<CMainFrame>)

END_MSG_MAP()

BEGIN_UPDATE_UI_MAP(CMainFrame) //菜单及工具条的更新

UPDATE_ELEMENT(ID_VIEW_TOOLBAR, UPDUI_MENUPOPUP)

UPDATE_ELEMENT(ID_VIEW_STATUS_BAR, UPDUI_MENUPOPUP)

END_UPDATE_UI_MAP()

};

在框架窗口类中唯一最重要的函数是OnCreate。它初始化CComandBarCtrl对象,并与菜单关联、装入命令条图像列表(菜单上的图标)。实际上,CComandBarCtrl类把资源文件中菜单描述转换为工具条元素,它可以很容易地为菜单项和工具条按钮关联同一个命令ID和图标。然后,框架窗口继续创建工具条、伸缩条(rebar)和状态条。接着是初始化视图。最后一步是加入框架窗口的消息过滤器(message filter)和空闲处理器(idle handler)到CComModule类的应用程序对象中。消息过滤是这样一种技术,在你的程序中,在GetMessage函数将消息成功放入你的消息队列之后Translate/DispatchMessage函数处理它之前,在各个窗口之间路由消息。

LRESULT OnCreate(UINT /*uMsg*/, WPARAM /*wParam*/,

LPARAM /*lParam*/, BOOL& /*bHandled*/)

{

HWND hWndCmdBar = m_CmdBar.Create(m_hWnd,

rcDefault,

NULL,

ATL_SIMPLE_CMDBAR_PANE_STYLE);

m_CmdBar.AttachMenu(GetMenu());

m_CmdBar.LoadImages(IDR_MAINFRAME);

SetMenu(NULL); //删除老式的菜单

HWND hWndToolBar = CreateSimpleToolBarCtrl(m_hWnd,

IDR_MAINFRAME,

FALSE,

ATL_SIMPLE_TOOLBAR_PANE_STYLE);

CreateSimpleReBar(ATL_SIMPLE_REBAR_NOBORDER_STYLE);

AddSimpleReBarBand(hWndCmdBar);

AddSimpleReBarBand(hWndToolBar, NULL, TRUE);

CreateSimpleStatusBar();

m_hWndClient = m_view.Create(m_hWnd, rcDefault, NULL,

WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS |

WS_CLIPCHILDREN, WS_EX_CLIENTEDGE);

UIAddToolBar(hWndToolBar);

UISetCheck(ID_VIEW_TOOLBAR, 1);

UISetCheck(ID_VIEW_STATUS_BAR, 1);

CMessageLoop* pLoop = _Module.GetMessageLoop();

pLoop->AddMessageFilter(this);

pLoop->AddIdleHandler(this);

return 0;

}

视图类派生于CWindowImpl类,应用程序向导为它产生一个消息处理器 - 为WM_PAINT - 带着TODO注释:

class CHelloWorldView: public CWindowImpl<CHelloWorldView>

{

public:

DECLARE_WND_CLASS(NULL)

BOOL PreTranslateMessage(MSG* pMsg)

{

pMsg;

return FALSE;

}

BEGIN_MSG_MAP(CHelloWorldView)

MESSAGE_HANDLER(WM_PAINT, OnPaint)

END_MSG_MAP()

LRESULT OnPaint(UINT /*uMsg*/, WPARAM /*wParam*/,

LPARAM /*lParam*/, BOOL& /*bHandled*/)

{

CPaintDC dc(m_hWnd);

//TODO: Add your drawing code here

return 0;

}

};

在这里,作一个小的改动。加入TextOut函数到OnPaint函数中,以输入“Hello World”字符串,编译运行它。

将这个WTL版的程序与前一章(ATL窗口第一部分)的程序比较。我们的轻松来自于向导为我们做了些讨厌的工作。但不要忘了所有这些帮助我们是免费获得的 - 框架+视图、漂亮的菜单、关于对话框、工具条和状态条、包括显示/隐藏功能和更新用户界面处理。同样,与MFC的等价物相比较 - MFC应用程序向导会给你菜单、工具条、状态条和关于对话框,但在MFC中把工具条按钮图标加入到菜单中容易吗?而且,再比较可执行文件的大小,特别是发行版。

在我文章的最后,我想效仿上一章中的ATL Scribble应用程序。

好,如果你在类视图(ClassView)中点击鼠标右键,选择“Add Windows Message Handler”,然后再选择WM_LBUTTONDOWN处理函数,会产生以下代码:

LRESULT OnLButtonDown(UINT uMsg,

WPARAM wParam,

LPARAM lParam,

BOOL& bHandled)

{

return 0;

}

消息映射条目如下:

MESSAGE_HANDLER(WM_LBUTTONDOWN, OnLButtonDown)

自从我们使用ATL之后,向导不再提供MFC所用的分类描述消息映射(cracked messages)的宏。“请暂停一下”我听见你说,“你的文章不是说WTL为我们提供了消息分类描述宏吗?”是的,WTL提供了类似于MFC的宏,在ATLCRACK.H文件中。如果查看源代码,你会看见与Windows消息相对应的宏。你所要做的就是使用适当的宏,并实现相同格式的消息处理函数。还有,你要使用BEGIN_MSG_MAP_EX来代替BEGIN_MSG_MAP宏。它为分类描述消息处理函数(cracked handlers)提供了重新得到当前消息和指定消息是否被处理。这是因为分类描述消息处理函数没有ATL处理函数该有的最后一个布尔(bool)参数。因此,BEGIN_MSG_MAP_EX宏定义了一个额外的函数SetMessageHandled来完成此工作。例如,响应WM_LBUTTONDOWN消息的宏如下:

#define MSG_WM_LBUTTONDOWN(func) \

if (uMsg == WM_LBUTTONDOWN) \

{ \

SetMsgHandled(TRUE); \

func((UINT)wParam, CPoint(GET_X_LPARAM(lParam), \

GET_Y_LPARAM(lParam))); \

lResult = 0; \

if(IsMsgHandled()) \

return TRUE; \

}

注意,这里使用了CPoint,因此要包含ATLMISC.H。

因此,在你的HelloWorldView.h文件最开始处加入#include "atlmisc.h"和"atlcrack.h"。动手将你的视图的消息映射宏改为EX版本,并加下如下的分类消息宏和处理函数。记住,如果你想使用像CPen一样的GDI对象,请将#include "atlgdi.h"加入到文件中。定义两个CPoint对象m_startPoint、m_endPoint到视图中,并在构造函数中初始化为(-1,-1)。

BEGIN_MSG_MAP_EX(CHelloWorldView)

MSG_WM_LBUTTONDOWN(OnLButtonDown)

MSG_WM_LBUTTONUP(OnLButtonUp)

MSG_WM_MOUSEMOVE(OnMouseMove)

END_MSG_MAP()

LRESULT OnLButtonDown (UINT flags, CPoint point)

{

m_startPoint = point;

return 0;

}

LRESULT OnLButtonUp (UINT flags, CPoint point)

{

m_startPoint.x = m_startPoint.y = -1;

return 0;

}

LRESULT OnMouseMove (UINT flags, CPoint point)

{

m_endPoint = point;

CClientDC dc(this->m_hWnd);

CPen np;

np.CreatePen(PS_SOLID, 2, RGB(255,0,0));

HPEN op = dc.SelectPen(np.m_hPen);

if (m_startPoint.x != -1 )

{

dc.MoveTo(m_startPoint.x, m_startPoint.y, NULL);

dc.LineTo(m_endPoint.x, m_endPoint.y);

m_startPoint.x = m_endPoint.x;

m_startPoint.y = m_endPoint.y;

}

dc.SelectPen(op);

return 0;

}

为了改变颜色和画笔,象上一篇文章(ATL窗口第1部分)中一样,我们加入简单的菜单/工具条支持。加入一个新的菜单,“Color”下有三个菜单项“Red”、“Green”和“Blue”。同时,也加入相应的工具条按钮,并确信它们的ID是一致的。编写改变视图中COLORREF类型的成员变量的值的命令处理函数。加入这些命令处理函数的消息映射条目到BEGIN_MSG_MAP_EX/END_MSG_MAP中。

COMMAND_ID_HANDLER_EX(ID_COLOR_RED, OnColorRed)

COMMAND_ID_HANDLER_EX(ID_COLOR_GREEN, OnColorGreen)

COMMAND_ID_HANDLER_EX(ID_COLOR_BLUE, OnColorBlue)

LRESULT OnColorRed(UINT, int, HWND)

{

m_color = RGB(255,0,0);

return 0;

}

LRESULT OnColorGreen(UINT, int, HWND)

{

m_color = RGB(0,255,0);

return 0;

}

LRESULT OnColorBlue(UINT, int, HWND)

{

m_color = RGB(0,0,255);

return 0;

}

如果你这个时候运行程序,你会发现菜单和工具条上这些新的条目可以使用,但却没有任何结果,原来菜单产生的消息并没有传递到视图类中。为什么?哦,命令消息源自框架类,所以菜单(工具条)产生的消息只会传递给框架类。ATL/WTL使用了一种不同于MFC的消息路由策略使消息从一个类传递到另一个类,你必须加入以下的宏到框架类的消息映射中(细节请看ATL窗口第1部分)。

CHAIN_MSG_MAP_MEMBER(m_view)

有些东西是与MFC不同的,尽管你已看到这些扩展来自于ATL。重要的美景是什么?认真的开发员不必太拘泥于很小的向导支持。另一方面,漂亮的菜单(cool menu)是很漂亮,但它真的值得从MFC改变过来?是的,WTL可以说是ATL++,ATL是重要的基于COM的开发工具。这是否就是使用它的充分理由?毕竟,WTL不被微软正式支持。

对于WTL,你需要些什么支持?微软使用WTL的早期版本已经几年了,因为用它开发的软件很小很高效,而且微软ATL/WTL小组和广大的ATL/WTL社团都承诺继续支持WTL。

ATL/WTL并不会很快取代MFC,但许多项目要求更快的生产效率,更快的运行效率,更少的消耗,加上对COM的轻松支持,ATL/WTL代替MFC是可能的。我使用MFC已经10年了,但ATL/WTL组合是多么的诱人。如果保留我们对旧的技术的投资是一条标准,我们可能还在用COBOL。我们做的东西已被大家接受,你还想到哪里去呢?

本文地址:http://com.8s8s.com/it/it3186.htm