Party II WTL GUI Base Classes
WTL Overview
WTL类主要分成一下5类:
1. Frame窗口实现: CFrameWindowImpl, CMDIFrameWindowImple
2. 空间封装:CButton, CListViewCtrl
3. GDI封装:CDC, CMenu
4. 特殊UI特征:CSplitterWindow, CUpdateUI, CDialogResize, CCustomDraw
5. 工具类和宏:CString , CRect, BEGIN_MSG_MAP_EX
其中多数是独立类,但也包括嵌入类如: CDialogResize。
Beginning a WTL EXE
// stdafx.h
#define STRICT
#define WIN32_LEAN_AND_MEAN
#define _WTL_USE_CSTRING
#include <atlbase.h> // base ATL classes
#include <atlapp.h> // base WTL classes
extern CAppModule _Module; // WTL version of CComModule
#include <atlwin.h> // ATL GUI classes
#include <atlframe.h> // WTL frame window classes
#include <atlmisc.h> // WTL utility classes like CString
#include <atlcrack.h> // WTL enhanced msg map macros
Atlapp.h是第一个引入的WTL头文件,其中包含 了消息处理类、从CComModule继承来的CAppModule类。如果要用atlmisc.h中的CString类的话,需要定义_WTL_USE_CSTRING宏,在atlapp.h引入前定义,保证其中能使用CString。
CAppModule拥有有关空闲处理及UI更新的功能。
// 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()
};
Frame声明宏需要两个参数,一个窗口类名称、一个资源ID。通过资源ID,WTL可在创建窗体的时候加载图标、菜单、快捷键表,并可查找一个字符串作为窗口标题。
// 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()方法,该方法有大部分普通默认值,所以不需要参数;它会处理资源加载,我们只需要创建需要的资源就行。
WTL Message Map Enhancements
消息处理中复杂的一点是WPARAM/LPARAM需要自己解析,ATL也未做改进,WTL在这方面有了提高。
WTL增强消息映射宏在atlcrack.h中,在VC6和VC7中使用稍有不同:
对于ATL3.0, 使用增强消息处理的话需要用BEGIN_MSG_MAP_EX;
对于ATL7.0/7.1,对于CWindowImpl/CDialogImpl的继承子类需使用BEGIN_MSG_MAP,对于非CWindowImpl/CDialogImpl继承子类需使用BEGIN_MSG_MAP_EX。
// MyWindow.h, VC6 only:
class CMyWindow : public CFrameWindowImpl<CMyWindow>
{
public:
BEGIN_MSG_MAP_EX(CMyWindow)
CHAIN_MSG_MAP(CFrameWindowImpl<CMyWindow>)
END_MSG_MAP()
};
记住VC7中对于窗口子类的消息映射不需要_EX,就可以使用增强消息映射。
WTL消息处理名称: MSG_ + 消息名称。 如: MSG_WM_CREATE,这个宏只需要消息处理函数名作为参数。
Class CMyWindwo : 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_EX()
LRESULT OnCreate(LPCREATESTRUCT lpcs)
{
SetTimer(1, 1000);
SetMsgHandled(false);
Return 0;
}
Void OnDestroy()
{
KillTimer(1);
SetMsgHandled(false);
}
Void OnTimer(UNIT uTimerID, TIMERPROC pTimerProc)
{
If( 1 != uTimerID)
SetMsgHnadled(false);
Else
RedrawWindow();
}
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 thewindow
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
}
};
WTL的消息处理函数和MFC中的相似,都对应着固定原型:具有参数和返回值。由于没有向导生成处理函数,所以需要我们自己找到函数原型,通过“将光标移至消息宏上,按F12”就可以转到宏定义处。 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中设置SetMsgHandled(false)让父类处理该消息,尽管父类不处理该消息,都这么做,就不需要记住哪些父类是处理该消息的。
如果要添加WM_COMMAND类型的消息映射,采用COMMAND_ID_HANDLER_EX宏。
class CMainFrame : public CFrameWindowImpl<CMainFrame>,
public CUpdateUI<CMainFrame>,
public CMessageFilter,
public CIdleHandler
{
public:
DECLARE_FRAME_WND_CLASS(NULL, IDR_MAINFRAME)
BEGIN_UPDATE_UI_MAP(CMainFrame)
END_UPDATE_UI_MAP()
BEGIN_MSG_MAP(CMainFrame)
// ...
CHAIN_MSG_MAP(CUpdateUI<CMainFrame>)
CHAIN_MSG_MAP(CFrameWindowImpl<CMainFrame>)
END_MSG_MAP()
BOOL PreTranslateMessage(MSG* pMsg);
BOOL OnIdle();
protected:
CWTLClockView m_view;
};
CMessageFilter是一个嵌入类提供PreTranslateMessage()方法(感觉像interface),CIdleHandler是另一个提供OnIdle()方法的嵌入类。CMessageLoop、CIdleHandler和CUpdateUI一起实现了MFC中ON_UPDATE_COMMAND_UI的功能。
OnCreate创建并保存视图的句柄,这样Frame窗体改变大小的时候视图大小也随着改变。同时将将本Frame窗体对象加入消息过滤和空闲处理列表。
CMessageLoop Internals
CMessageLoop为应用程序提供了消息泵功能。除了标准的TranslateMessage/DispatchMessage循环歪,通过PreTranslateMessage()提供了消息过滤功能以及通过OnIdle()提供了空闲处理功能。其机制如:
int Run()
{
MSG msg;
for(;;)
{
while ( !PeekMessage(&msg) )
CallIdleHandlers();
if ( 0 == GetMessage(&msg) )
break; // WM_QUIT retrieved from the queue
if ( !CallPreTranslateMessageFilters(&msg) )
{
// if we get here, message was not filtered out
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
return msg.wParam;
}
窗体通过将自身注册到Module中CMessageLoop中,这样消息泵就知道该调用那个窗体的PreTranslateMessage()方法和OnIdle()方法。
注意:在消息循环中没有对TranslateAccelerator()或IsDialogMessage()的调用。CFrameWindowImple处理了TranslateAccelerator方法调用,但是如果在程序中使用了无模式对话框,就需要自己在CMainFrame::PreTranslateMessage()方法中判断IsDialogMessage()。
CFrameWindowImpl Internals
CFrameWindowImple及其基类CFrameWindowImplBase提供了原来在MFC Frame窗体中很多特征:工具栏、rebars、状态栏工具提示、菜单项提示。m_hWndClient是CFrameWindowImplBase中视图窗口的句柄。
看一下CFrameWindowImple中WM_SIZE的处理:
LRESULT OnSize(UNIT /*UmSG*/, WPARAM wParam, LPARAM /*lParam*/, BOOL& bHandled)
{
If(wParam != SIZE_MINIMIZED)
{
T* pT = static_cast<T*>(this);
pT->UpdateLayout();
}
bHandled = false;
return 1;
}
Void UpdateLayout(BOOL bResizeBars = true)
{
RECT rect;
GetClientRect(&rect);
// position bars and offset theirdimensions
UpdateBarsPosition(rect, bResizeBars);
// resize client window
If(m_hWndClient != NULL)
::SetWindowPos(m_hWndClient, NULL, rect.left, rect.top, rect.right-rect.left, rect.bottom – rect.top, SWP_NOZORDER | SWP_NOACTIVATE);
}
Back to the Clock Program
class CWTLClockView : public CWindowImpl<CWTLClockView>
{
public:
DECLARE_WND_CLASS(NULL)
BOOL PreTranslateMessage(MSG* pMsg);
BEGIN_MSG_MAP_EX(CWTLClockView)
MESSAGE_HANDLER(WM_PAINT, OnPaint)
MSG_WM_CREATE(OnCreate)
MSG_WM_DESTROY(OnDestroy)
MSG_WM_TIMER(OnTimer)
MSG_WM_ERASEBKGND(OnEraseBkgnd)
END_MSG_MAP()
};
注意:当使用BEGIN_MSG_MAP_EX宏(即使用增强消息映射时),消息映射宏也需要改成WTL版本。
UI Updating
UI空闲时间更新由:CMessageLoop对象、CIdleHandler、CUpdateUI和UPDATE_UI_MAP宏一起完成。CUpdateUI可以操作5种不同类型的元素,每种类型对应CUpdateUIBase中常量:
1. UPDUI_MENUBAR 菜单项
2. UPDUI_MENUPOPUP 弹出菜单
3. UPDUI_TOOBAR 工具栏
4. UPDUI_STATUSBAR状态栏
5. UPDUI_CHILDWINDOW 子窗口
可以设置enabled状态、checked状态和文本。
要使用UI更新,需要做四件事:
1. 窗体从CUpdateUI和CIdleHandler继承
2. 消息链入CUpdateUI
3. 将窗体对象添加到Module的空闲处理列表
4. 添加UPDATE_UI_MAP映射
New menu items to control the clock
增加两个时钟控制菜单:IDC_START/IDC_STOP
Class CMainFrame : public …
{
Public:
// …
BEGIN_UPDATE_UI_MAP(CMainFrame)
UPDATE_ELEMENT(IDC_START, UPDI_MENUPOPUP)
UPDATE_ELEMENT(IDC_STOP, UPDT_MENUPOPUP)
END_UPDATE_UI_MAP()
// …
};
这样就可以通过CUpdteUI::UIEnable()通过传菜单id和bool值来改变菜单enabled状态。
Calling UIEnable()
LRESULT CMainFrame::OnCreate(UINT /*uMsg*/, WPARAM /*wParam*/,
LPARAM /*lParam*/, BOOL& /*bHandled*/)
{
m_hWndClient = m_view.Create(...);
// register object for message filtering and idle updates
// [omitted for clarity]
// Set the initial state of the Clock menu items:
UIEnable ( IDC_START, false );
UIEnable ( IDC_STOP, true );
return 0;
}
CMainFrame需要处理菜单消息,改变菜单状态(因为菜单是主窗体的),然后调用视图作出响应动作。
class CMainFrame : public ...
{
public:
BEGIN_MSG_MAP_EX(CMainFrame)
// ...
COMMAND_ID_HANDLER_EX(IDC_START, OnStart)
COMMAND_ID_HANDLER_EX(IDC_STOP, OnStop)
END_MSG_MAP()
// ...
void OnStart(UINT uCode, int nID, HWND hwndCtrl);
void OnStop(UINT uCode, int nID, HWND hwndCtrl);
};
void CMainFrame::OnStart(UINT uCode, int nID, HWND hwndCtrl)
{
// Enable Stop and disable Start
UIEnable ( IDC_START, false );
UIEnable ( IDC_STOP, true );
// Tell the view to start its clock.
m_view.StartClock();
}
void CMainFrame::OnStop(UINT uCode, int nID, HWND hwndCtrl)
{
// Enable Start and disable Stop
UIEnable ( IDC_START, true );
UIEnable ( IDC_STOP, false );
// Tell the view to stop its clock.
m_view.StopClock();
}