您的位置:首页 > 其它

WTL学习笔记——(4)WTL界面基类

2009-11-03 08:46 260 查看
一、WTL 总体印象

WTL的类大致可以分为几种类型:

1、主框架窗口的实现
-
CFrameWindowImpl, CMDIFrameWindowImpl

2、控件的封装- CButton, CListViewCtrl GDI

3、对象的封装- CDC, CMenu

4、一些特殊的界面特性 - CSplitterWindow, CUpdateUI, CDialogResize, CCustomDraw

5、实用的工具类和宏- CString, CRect, BEGIN_MSG_MAP_EX

二、开始写WTL程序

如果你没有用WTL的应用程序生成向导也没关系(我将在后面介绍这个向导的用法), WTL的程序的代码结构很像ATL的程序,本章使用的例子代码有别于第一章的例子,主要是为了显示WTL的特性,没有什么实用价值。

这一节我们将在WTL生成的代码基础上添加代码,生成一个新的程序,程序主窗口的客户区显示当前的时间。stdafx.h的代码如下:

#define STRICT
#define WIN32_LEAN_AND_MEAN
#define _WTL_USE_CSTRING

#include <atlbase.h>       // 基本的ATL类
#include <atlapp.h>        // 基本的WTL类
extern CAppModule _Module; // WTL 派生的CComModule版本
#include <atlwin.h>        // ATL 窗口类
#include <atlframe.h>      // WTL 主框架窗口类
#include <atlmisc.h>       // WTL 实用工具类,例如:CString
#include <atlcrack.h>      // WTL 增强的消息宏


atlapp.h 是你的工程中第一个包含的头文件,这个文件内定义了有关消息处理的类和CAppModule,CAppModule是从CComModule派生的类。如 果你打算使用CString类,你需要手工定义_WTL_USE_CSTRING标号,因为CString类是在atlmisc.h中定义的,而许多包含 在atlmisc.h之前的头文件都会用到CString,定义_WTL_USE_CSTRING之后,atlapp.h就会向前声明CString类, 其他的头文件就知道CString类的存在,从而避免编译器为此大惊小怪。

接下来定义框架窗口。我们的SDI窗口是从CFrameWindowImpl派生的,在定义窗口类时使用DECLARE_FRAME_WND_CLASS代替前面使用的DECLARE_WND_CLASS。下面时MyWindow.h中窗口定义的开始部分:

class CMyWindow : public CFrameWindowImpl<CMyWindow>
{
public:
DECLARE_FRAME_WND_CLASS(_T("First WTL window"), IDR_MAINFRAME);    BEGIN_MSG_MAP(CMyWindow)        CHAIN_MSG_MAP(CFrameWindowImpl<CMyWindow>)    END_MSG_MAP()};


DECLARE_FRAME_WND_CLASS有两个参数,窗口类名(类名可以是NULL,ATL会替你生成一个类名)和资源ID,创建窗口时 WTL用这个ID装载图标,菜单和加速键表。我们还要象CFrameWindowImpl中的消息处理(例如WM_SIZE和WM_DESTROY消息) 那样将消息链入窗口的消息中。

现在来看看WinMain()函数,它和第一部分中的例子代码中的WinMain()函数几乎一样,只是创建窗口部分的代码略微不同。

// main.cpp:
#include "stdafx.h"
#include "MyWindow.h"

CAppModule _Module;

int APIENTRY WinMain ( HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow )
{
_Module.Init ( NULL, hInstance );

CMyWindow wndMain;
MSG msg;

// Create the main window
if ( NULL == wndMain.CreateEx() )
return 1;       // Window creation failed

// Show the window
wndMain.ShowWindow ( nCmdShow );
wndMain.UpdateWindow();

// Standard Win32 message loop
while ( GetMessage ( &msg, NULL, 0, 0 ) > 0 )
{
TranslateMessage ( &msg );
DispatchMessage ( &msg );
}

_Module.Term();
return msg.wParam;
}


CFrameWindowImpl中的CreateEx()函数的参数使用了常用的默认值,所以我们不需要特别指定任何参数。正如前面介绍 的,CFrameWindowImpl会处理资源的装载,你只需要使用IDR_MAINFRAME作为ID定义你的资源就行了(译者注:主要是图标,菜单 和加速键表),你也可以直接使用本章的例子代码。

如果你现在就运行程序,你会看到主框架窗口,事实上它没有做任何事情。我们需要手工添加一些消息处理,所以现在是介绍WTL的消息映射宏的最佳时间。

三、WTL 对消息映射的增强

将Win32 API通过消息传递过来的WPARAM和LPARAM数据还原出来是一件麻烦的事情并且很容易出错,不幸得是ATL并没有为我们提供更多的帮助,我们仍然需要从消息中还原这些数据,当然WM_COMMAND和WM_NOTIFY消息除外。但是WTL的出现拯救了这一切!

WTL的增强消息映射宏定义在atlcrack.h中。(这个名字来源于“消息解密者”,是一个与windowsx.h的宏所使用的相同术语)首先将BEGIN_MSG_MAP改为BEGIN_MSG_MAP_EX,带_EX的版本产生“解密”消息的代码。

class CMyWindow : public CFrameWindowImpl<CMyWindow>
{
public:
BEGIN_MSG_MAP_EX(CMyWindow)
CHAIN_MSG_MAP(CFrameWindowImpl<CMyWindow>)
END_MSG_MAP()
};


对于我们的时钟程序,我们需要处理WM_CREATE消息来设置定时器,WTL的消息处理使用MSG_作为前缀,后面是消息名称,例如MSG_WM_CREATE。这些宏只是代表消息响应处理的名称,现在我们来添加对WM_CREATE消息的响应:

class CMyWindow : public CFrameWindowImpl<CMyWindow>
{
public:
BEGIN_MSG_MAP_EX(CMyWindow)
MSG_WM_CREATE(OnCreate)
CHAIN_MSG_MAP(CFrameWindowImpl<CMyWindow>)
END_MSG_MAP()

// OnCreate(...) ?
};


WTL的消息响应处理看起来有点象MFC,每一个处理函数根据消息传递的参数不同也有不同的原型。由于我们没有向导自动添加消息响应,所以我们需要 自己查找正确的消息处理函数。幸运的是VC可以帮我们的忙,将鼠标光标移到“MSG_WM_CREATE”宏的文字上按F12键就可以来到这个宏的定义代 码处。如果是第一次使用这个功能,VC会要求从新编译全部文件以建立浏览信息数据库(browse info database),建立了这个数据库之后,VC会打开atlcrack.h并将代码定位到MSG_WM_CREATE的定义位置:

#define MSG_WM_CREATE(func) /
if (uMsg == WM_CREATE) /
{ /
SetMsgHandled(TRUE); /
lResult = (LRESULT)func((LPCREATESTRUCT)lParam); /
if(IsMsgHandled()) /
return TRUE; /
}


标记为红色的那一行非常重要,就是在这里调用实际的消息响应函数,他告诉我们消息响应函数有一个
LPCREATESTRUCT
类型的参数,返回值的类型是LRESULT。请注意这里没有ATL的宏所用的 bHandled 参数,
SetMsgHandled()
函数代替了这个参数,我会对此作些简要的介绍。

现在为我们的窗口类添加OnCreate()响应函数:

class CMyWindow : public CFrameWindowImpl<CMyWindow>
{
public:
BEGIN_MSG_MAP_EX(CMyWindow)
MSG_WM_CREATE(OnCreate)
CHAIN_MSG_MAP(CFrameWindowImpl<CMyWindow>)
END_MSG_MAP()

LRESULT OnCreate(LPCREATESTRUCT lpcs)
{
SetTimer ( 1, 1000 );
SetMsgHandled(false);
return 0;
}
};


CFrameWindowImpl 是直接从CWindow类派生的, 所以它继承了所有CWindow类的方法,如SetTimer()。这使得对窗口API的调用有点象MFC的代码,只是MFC使用CWnd类包装这些API。

我们使用SetTimer()函数创建一个定时器,它每隔一秒钟(1000毫秒)触发一次。由于我们需要让CFrameWindowImpl也处理 WM_CREATE消息,所以我们调用SetMsgHandled(false),让消息通过CHAIN_MSG_MAP宏链入基类,这个调用代替了 ATL宏使用的bHandled参数。(即使CFrameWindowImpl类不需要处理WM_CREATE消息,调用 SetMsgHandled(false)让消息流入基类是个好的习惯,因为这样我们就不必总是记着哪个消息需要基类处理那些消息不需要基类处理,这和 VC的类向导产生的代码相似,多数的派生类的消息处理函数的开始或结尾都会调用基类的消息处理函数)

为了能够停止定时器我们还需要响应WM_DESTROY消息,添加消息响应的过程和前面一样,MSG_WM_DESTROY宏的定义是这样的:

#define MSG_WM_DESTROY(func) /
if (uMsg == WM_DESTROY) /
{ /
SetMsgHandled(TRUE); /
func(); /
lResult = 0; /
if(IsMsgHandled()) /
return TRUE; /
}


OnDestroy()函数没有参数也没有返回值,CFrameWindowImpl也要处理WM_DESTROY消息,所以还要调用SetMsgHandled(false):

class CMyWindow : public CFrameWindowImpl<CMyWindow>
{
public:
BEGIN_MSG_MAP_EX(CMyWindow)
MSG_WM_CREATE(OnCreate)
MSG_WM_DESTROY(OnDestroy)
CHAIN_MSG_MAP(CFrameWindowImpl<CMyWindow>)
END_MSG_MAP()

void OnDestroy()
{
KillTimer(1);
SetMsgHandled(false);
}
};


接下来是响应WM_TIMER消息的处理函数,它每秒钟被调用一次。你应该知道怎样使用F12键的窍门了,所以我直接给出响应函数的代码:

class CMyWindow : public CFrameWindowImpl<CMyWindow>
{
public:
BEGIN_MSG_MAP_EX(CMyWindow)
MSG_WM_CREATE(OnCreate)
MSG_WM_DESTROY(OnDestroy)
MSG_WM_TIMER(OnTimer)
CHAIN_MSG_MAP(CFrameWindowImpl<CMyWindow>)
END_MSG_MAP()

void OnTimer ( UINT uTimerID, TIMERPROC pTimerProc )
{
if ( 1 != uTimerID )
SetMsgHandled(false);
else
RedrawWindow();
}
};


这个响应函数只是在每次定时器触发时重画窗口的客户区。最后我们要响应WM_ERASEBKGND消息,在窗口客户区的左上角显示当前的时间。

class CMyWindow : public CFrameWindowImpl<CMyWindow>
{
public:
BEGIN_MSG_MAP_EX(CMyWindow)
MSG_WM_CREATE(OnCreate)
MSG_WM_DESTROY(OnDestroy)
MSG_WM_TIMER(OnTimer)
MSG_WM_ERASEBKGND(OnEraseBkgnd)
CHAIN_MSG_MAP(CFrameWindowImpl<CMyWindow>)
END_MSG_MAP()

LRESULT OnEraseBkgnd ( HDC hdc )
{
CDCHandle  dc(hdc);
CRect      rc;
SYSTEMTIME st;
CString    sTime;

// Get our window''s client area.
GetClientRect ( rc );

// Build the string to show in the window.
GetLocalTime ( &st );
sTime.Format ( _T("The time is %d:%02d:%02d"), st.wHour, st.wMinute, st.wSecond );

// Set up the DC and draw the text.
dc.SaveDC();
dc.SetBkColor ( RGB(255,153,0);
dc.SetTextColor ( RGB(0,0,0) );
dc.ExtTextOut ( 0, 0, ETO_OPAQUE, rc, sTime, sTime.GetLength(), NULL );

// Restore the DC.
dc.RestoreDC(-1);
return 1;	// We erased the background (ExtTextOut did it)
}
};


这个消息处理函数不仅使用了CRect和CString类,还使用了一个GDI包装类CDCHandle。对于CString类我想说的是它等同与 MFC的CString类,我在后面的文章中还会介绍这些包装类,现在你只需要知道CDCHandle是对HDC的简单封装就行了,使用方法与MFC的 CDC类相似,只是CDCHandle的实例在超出作用域后不会销毁它所操作的设备上下文。

四、程序运行效果

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息