WTL入門(1)-- ATL背景知識
本文適用於對MFC比較瞭解的中級開發人員。
原始碼下載:http://download.csdn.net/source/3522785
ATL Background
ATL-style templates
class CMyWnd : public CWindowImpl<CMyWnd>
{
...
};
看下面例子:
template <class T>
class B1
{
public:
void SayHi()
{
// 此處是關鍵技巧。
T* pT = static_cast<T*>(this);
pT->PrintClassName();
}
void PrintClassName() { cout << "This is B1"; }
};
class D1 : public B1<D1>
{
// No overridden functions at all
};
class D2 : public B1<D2>
{
void PrintClassName() { cout << "This is D2"; }
};
int main()
{
D1 d1;
D2 d2;
d1.SayHi(); // prints "This is B1"
d2.SayHi(); // prints "This is D2"
return 0;
}
static_cast<T*
>(
this)
是關鍵
技術。它將
B1*
的
this
根據不同的呼叫轉化為
D1*
或
D2*
。由於模板程式碼是在編譯時生成的,只要繼承列表編寫正確,這種型別轉換就是安全的。
(
避免寫出
class
D3 :
public B1
<D2
>這種形式的程式碼。編譯器是檢測不出的。)
第一次呼叫
SayHi()
時,程式碼實際是這樣的:
void B1<D1>::SayHi()
{
D1* pT = static_cast<D1*>(this);
pT->PrintClassName();
}
D1沒有重寫PrintClassName(),因此去搜尋D1的基類。B1有PrintClassName(),因此實際呼叫B1::PrintClassName()。
void B1<D2>::SayHi()
{
D2* pT = static_cast<D2*>(this);
pT->PrintClassName();
}
D2重寫了函式PrintClassName(),直接呼叫此函式。
這種技術的優點:
1) 不需要使用物件的指標
2) 節省記憶體,因為不需要使用虛擬函式表
3) 不會因為未初始化的虛擬函式表導致使用NULL指標
4) 所有函式的呼叫在編譯時確定,因此它們是可以優化的。
ATL Windowing Classes
ATL嚴格遵守介面-實現分離的原則。
ATL擁有一個定義Window的介面類:CWindow。它僅僅封裝了HWND,並且封裝了幾乎所有的Use32 APIs中以HWND為第一個引數的介面。例如SetWindowText()
和DestroyWindow()
。
CWindow
提供一個共有的成員
m_hWnd
,可以直接處理
HWND
,也提供了一個
operator
HWND()
,可以直接是
CWindow
作為需要
HWND
物件的函式引數。
CWindow
不同於
MFC
中的
CWnd
。
CWindow
易於建立的,它不提供像
MFC
中的
HWND
到
CWnd
的物件關係。當
CWindow
物件超出作用域時,它被銷燬,但是它關聯的實際視窗不會被銷燬。因此不需要
detach
你建立的臨時的
CWindow
物件。
ATL
還提供了一個
Window
的實現類
CWindowImpl
。它包含了下列處理:視窗註冊、視窗子類、訊息對映、以及一個基本的
WindowProc().
不想
MFC
中的
CWnd
,所有的東西都在這個類中。
關於對話方塊的實現,
ATL
提供了兩個獨立的實現類
CDialogImpl 和 CAxDialogImpl。前者用於普通對話方塊 ,後者用於ActiveX控制元件。
Defining a Window Implementation
定義一個非對話方塊的視窗類,要從CWindowImpl派生。新類中必須包含三件事情:
1) 視窗類定義
2) 訊息對映
3) 一個預設的窗體特徵,叫做window traits
1、
視窗類定義用巨集DECLARE_WND_CLASS
或 DECLARE_WND_CLASS_EX.
二者均定義了一個
ATL
結構
CWndClassInfo
,它封裝了
WNDCLASSEX
結構。前者僅僅定義新的視窗類的名字,其他引數用預設值;後者還可以定義視窗風格和背景色。視窗類的名字可以是
NULL
,此時
ATL
會自動生成一個。
class CMyWindow : public CWindowImpl<CMyWindow>
{
public:
DECLARE_WND_CLASS(_T("My Window Class"))
};
2、 訊息對映,與MFC類似。ATL 訊息對映將之擴充套件為一個Switch語句,選擇正確的控制程式碼執行對應的函式。
class CMyWindow : public CWindowImpl<CMyWindow>
{
public:
DECLARE_WND_CLASS(_T("My Window Class"))
BEGIN_MSG_MAP(CMyWindow)
END_MSG_MAP()
};
3、 定義window traits. 它是一個窗體風格和用於建立窗體的風格的組合。它們將作為模板引數來封裝窗體特徵類,這樣呼叫者不必困擾於當建立視窗時如何獲取正確的窗體風格。
typedef CWinTraits<WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN,
WS_EX_APPWINDOW> CMyWindowTraits;
class CMyWindow : public CWindowImpl<CMyWindow, CWindow, CMyWindowTraits>
{
public:
DECLARE_WND_CLASS(_T("My Window Class"))
BEGIN_MSG_MAP(CMyWindow)
END_MSG_MAP()
};
呼叫者可以重寫CMyWindowTraits的定義,但是這通常是不需要的。ATL也提供了一些預定義的窗體特徵類:如CFrameWinTraits
typedef CWinTraits<WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN |
WS_CLIPSIBLINGS,
WS_EX_APPWINDOW | WS_EX_WINDOWEDGE>
CFrameWinTraits;
Filling in the message map
ATL不提供像MFC那樣的自動生成訊息對映的巨集。但是僅僅有三種型別的訊息處理:一種像WM_NOTIFY
,
一種像
WM_COMMAND
,另一種就是其他型別的訊息處理。
第三類訊息
:
class CMyWindow : public CWindowImpl<CMyWindow, CWindow, CFrameWinTraits>
{
public:
DECLARE_WND_CLASS(_T("My Window Class"))
BEGIN_MSG_MAP(CMyWindow)
MESSAGE_HANDLER(WM_CLOSE, OnClose)
MESSAGE_HANDLER(WM_DESTROY, OnDestroy)
END_MSG_MAP()
LRESULT OnClose(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
DestroyWindow();
return 0;
}
LRESULT OnDestroy(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
PostQuitMessage(0);
return 0;
}
};
訊息處理函式封裝了WPARAM 和 LPARAM,在使用時需要對它們進行解碼。對於第四個引數,ATL在訊息處理之前會把它設為TRUE,如果你想讓ATL的預設WindowProc()
在
本訊息處理函式結束之後也處理此訊息,需要設定此引數為
FALSE
。這和
MFC
不同,你必須明確呼叫基類的訊息處理函式。
第二類訊息:WM_COMMAND
class CMyWindow : public CWindowImpl<CMyWindow, CWindow, CFrameWinTraits>
{
public:
DECLARE_WND_CLASS(_T("My Window Class"))
BEGIN_MSG_MAP(CMyWindow)
MESSAGE_HANDLER(WM_CLOSE, OnClose)
MESSAGE_HANDLER(WM_DESTROY, OnDestroy)
COMMAND_ID_HANDLER(IDC_ABOUT, OnAbout)
END_MSG_MAP()
LRESULT OnClose(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
DestroyWindow();
return 0;
}
LRESULT OnDestroy(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
PostQuitMessage(0);
return 0;
}
LRESULT OnAbout(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
{
MessageBox ( _T("Sample ATL window"), _T("About MyWindow") );
return 0;
}
};
COMMAND_HANDLER巨集把WPARAM 和 LPARAM已經解碼了。
同樣第一類訊息,通過NOTIFY_HANDLER
把
訊息引數解碼。
Advanced Message Maps and Mix-in Classes
ATL中的任一C++類均可處理訊息對映。這可以讓我們編寫混合類,我們通過新增此類到繼承列表中來為窗體新增新特性。
一個帶訊息對映的基類通常是一個把派生類名字作為模板引數的模板類。因此它可以處理派生類的成員,如m_hWnd(the HWND
member inCWindow
)。
下面一個例子是處理WM_ERASEBKGND
的一個混合類。
template <class T, COLORREF t_crBrushColor>
class CPaintBkgnd
{
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類有兩個模板引數,一個是將要用此類的派生類名字,另一個是背景色。(t_字首通常用於值型別模板引數)。建構函式和解構函式用於建立和銷燬畫刷。訊息對映處理WM_ERASEBKGND。最後,OnEraseBkgnd()實現用建構函式中建立的畫刷填充視窗。1)它呼叫了其派生視窗類中的函式GetClientRect(),
如果派生類沒有此函式,編譯器將會提示錯誤。2)OnEraseBkgnd()需要從wParam解碼出裝置上下文。
要使用此混合類,首先把它新增到繼承列表,然後用CHAIN_MSG_MAP
巨集
傳遞訊息到此類中。
class CMyWindow : public CWindowImpl<CMyWindow, CWindow, CFrameWinTraits>,
public CPaintBkgnd<CMyWindow, RGB(0,0,255)>
{
...
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()
...
};
任何到達CHAIN_MSG_MAP行還沒有被處理的訊息都會被傳入到CPaintBkgnd中。WM_CLOSE、WM_DESTROY和IDC_ABOUT將不會被連結到CPaintBkgnd,因為一旦訊息被處理,訊息路由的搜尋就結束了。typedef語句是必須的,因為CHAIN_MSG_MAP是一個只接受一個引數的預處理巨集,如果不這樣寫的話,前處理器將會認為它不只是一個引數。
一個視窗類可以有多個混合類,每一個有一個CHAIN_MSG_MAP巨集。
Structure of An ATL exe
以VC8為例:VC7以來,ATL標頭檔案自動宣告全域性的CComModule例項,以及初始化Init()和終止函式Term()。因此這些步驟就不需要了。
// stdafx.h:
#define STRICT
#define WIN32_LEAN_AND_MEAN
#include <atlbase.h> // Base ATL classes
#include <atlwin.h> // ATL windowing classes
WinMain()
函式不需要呼叫任何
_Module
函式
。
// main.cpp:
#include "MyWindow.h"
int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hInstPrev,
LPSTR szCmdLine, int nCmdShow)
{
CMyWindow wndMain;
MSG msg;
// Create & show our main window
if ( NULL == wndMain.Create ( NULL, CWindow::rcDefault,
_T("My First ATL Window") ))
{
// Bad news, window creation failed
return 1;
}
wndMain.ShowWindow(nCmdShow);
wndMain.UpdateWindow();
// Run the message loop
while ( GetMessage(&msg, NULL, 0, 0) > 0 )
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}
C++於處理器:WIN32;_WINDOWS。LINK子系統選擇Windows。
Dialogs in ATL
前面講過ATL有兩種Dialog。此處用CDialogImpl。建立一個新的對話方塊和建立一個新的框架視窗很相似。有兩點不同:
1) 用CDialogImpl替換CWindowImpl
2) 定義IDD指向對話方塊資源的ID
class CAboutDlg : public CDialogImpl<CAboutDlg>
{
public:
enum { IDD = IDD_ABOUT };
BEGIN_MSG_MAP(CAboutDlg)
END_MSG_MAP()
};
ATL沒有定義OK和CANCEL兩個button的處理,需要自己定義。處理WM_INITDIALOG訊息,初始化對話方塊的表現。
class CAboutDlg : public CDialogImpl<CAboutDlg>
{
public:
enum { IDD = IDD_ABOUT };
BEGIN_MSG_MAP(CAboutDlg)
MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog)
MESSAGE_HANDLER(WM_CLOSE, OnClose)
COMMAND_ID_HANDLER(IDOK, OnOKCancel)
COMMAND_ID_HANDLER(IDCANCEL, OnOKCancel)
END_MSG_MAP()
LRESULT OnInitDialog(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
CenterWindow();
return TRUE; // let the system set the focus
}
LRESULT OnClose(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
EndDialog(IDCANCEL);
return 0;
}
LRESULT OnOKCancel(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
{
EndDialog(wID);
return 0;
}
};
處理訊息window的WM_CREATE訊息,載入menu,響應選單ABOUT訊息彈出ABOUT對話方塊
class CMyWindow : public CWindowImpl<CMyWindow, CWindow, CFrameWinTraits>,
public CPaintBkgnd<CMyWindow,RGB(0,0,255)>
{
public:
BEGIN_MSG_MAP(CMyWindow)
MESSAGE_HANDLER(WM_CREATE, OnCreate)
COMMAND_ID_HANDLER(IDC_ABOUT, OnAbout)
// ...
CHAIN_MSG_MAP(CPaintBkgndBase)
END_MSG_MAP()
LRESULT OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
HMENU hmenu = LoadMenu ( _Module.GetResourceInstance(), // _AtlBaseModule in VC7
MAKEINTRESOURCE(IDR_MENU1) );
SetMenu ( hmenu );
return 0;
}
LRESULT OnAbout(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
{
CAboutDlg dlg;
dlg.DoModal();
return 0;
}
// ...
};
相關文章
- jQuery心得1--jQuery入門知識串講1jQuery
- Docker入門知識Docker
- HTTP入門知識HTTP
- .NET入門知識
- 爬蟲開發知識入門基礎(1)爬蟲
- Altium Designer 20 入門基礎知識(1)
- ATC背景知識
- 知識圖譜入門——知識表示與知識建模
- Blender入門知識整理
- Struts入門初步知識
- OkHttp 知識梳理(1) OkHttp 原始碼解析之入門HTTP原始碼
- Hibernate的入門知識
- Flutter2 入門知識Flutter
- HTTP協議_入門知識HTTP協議
- python入門基本知識Python
- JavaScript 基礎知識入門JavaScript
- 知識圖譜入門2
- docker入門知識總結Docker
- MySql入門--基礎知識MySql
- React入門知識點整理React
- Canvas快速入門知識點Canvas
- 機器學習入門知識體系機器學習
- css 入門基礎知識CSS
- MySQL5入門知識MySql
- 密碼學入門知識密碼學
- Java入門知識_Java初學者須知Java
- Python入門必知的知識點!Python基礎入門Python
- sql入門基礎知識分享SQL
- git 入門教程之知識速查Git
- 急速入門前端編碼知識前端
- Dubbo基礎入門知識點
- Java基礎知識入門-JDKJavaJDK
- Java入門基礎知識點Java
- shiro入門知識介紹
- WEB相關背景知識(新手)Web
- Python基礎知識入門(二)Python
- gitbook 入門教程之前置知識Git
- Python入門知識點彙總Python