摘要:討論Active Template Library (ATL) 3.0中的一些類,這些類圍繞著Windows API建立了一個面向?qū)ο蟮木幊炭蚣埽褂眠@個框架,可以簡化Microsoft® Windows®編程并且只需要很少的系統(tǒng)開銷。內(nèi)容包括:考察對窗口做了簡單封裝的CWindow類;使用CWindowImpl進(jìn)行消息處理和消息映射;使用ATL中的對話框類以及擴(kuò)展現(xiàn)有窗口類的功能的方法。
簡介:雖然Active Template Library (ATL)主要是為了支持COM開發(fā)而設(shè)計的,但它確實包含了很多可用于窗口設(shè)計的類。這些窗口類和ATL中的其它類一樣,都是基于模版的,并且只需要花費(fèi)很少系統(tǒng)開銷。這篇文章就向我們演示了使用ATL創(chuàng)建窗口和對話框并進(jìn)行消息處理的基本方法。
這篇文章假設(shè)讀者熟悉C++語言和Windows程序設(shè)計;但是并不一定要求讀者具有COM方面的知識。
CWindow:在ATL窗口類中,CWindow是最基本的。這個類對Windows API進(jìn)行了面向?qū)ο蟮陌b,它封裝了一個窗口句柄,并提供一些成員函數(shù)來操作它,這些函數(shù)包裝了相應(yīng)的Windows API。
標(biāo)準(zhǔn)的Windows程序設(shè)計看起來象這樣:
HWND hWnd = ::CreateWindow( "button", "Click me",
WS_CHILD, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, NULL, NULL, hInstance, NULL );
::ShowWindow( hWnd, nCmdShow );
::UpdateWindow( hWnd );
使用ATL中的CWindow類后,等效代碼如下:
CWindow win;
win.Create( "button", NULL, CWindow::rcDefault, "Click me",
WS_CHILD );
win.ShowWindow( nCmdShow );
win.UpdateWindow();
我們應(yīng)該在我們的大腦中我們應(yīng)該保持這樣一個概念:ATL的窗口對象與Windows系統(tǒng)中的窗口是不同的。Windows系統(tǒng)中的窗口指的是操作系統(tǒng)中維持的一塊數(shù)據(jù),操作系統(tǒng)靠這塊數(shù)據(jù)來操作屏幕上的一塊區(qū)域。而一個ATL窗口對象,是CWindow類的一個實例,它是一個C++對象,它的內(nèi)部沒有保存任何有關(guān)屏幕區(qū)域或者窗口數(shù)據(jù)結(jié)構(gòu)的內(nèi)容,只保存了一個窗口的句柄,這個句柄保存在它的數(shù)據(jù)成員m_hWnd中,CWindow對象和它在屏幕上顯示出來的窗口就是靠這個句柄聯(lián)系起來的。
理解了ATL中的窗口對象和Windows系統(tǒng)中窗口的區(qū)別,就更加容易理解CWindow對象的構(gòu)造與窗口的創(chuàng)建是兩個分開的過程。我們再看看前面的代碼,就會發(fā)現(xiàn),首先是一個CWindow對象被構(gòu)造:
CWindow win;
然后創(chuàng)建它的窗口:
win.Create( "button", NULL, CWindow::rcDefault, "Click me",
WS_CHILD );
我們也可以構(gòu)造一個CWindow對象,然后把它和一個已經(jīng)存在的窗口關(guān)聯(lián)起來,這樣我們就可以通過CWindow類的成員函數(shù)來操作這個已經(jīng)存在的窗口。這種方法非常有用,因為CWindow類提供的函數(shù)都是封裝好了的,用起來很方便,比如CWindow類中的CenterWindow, GetDescendantWindow等函數(shù)用起來就比直接使用Windows API方便得多。
HWND hWnd = CreateWindow( szWndClass, "Main window",
WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, NULL, NULL, hInstance, NULL );
// 下面的方法中可以任選一種:
// CWindow win( hWnd ); // 通過構(gòu)造函數(shù)關(guān)聯(lián)
// 或
// CWindow win;
// win = hWnd; // 通過賦值操作符關(guān)聯(lián)
// 或
// CWindow win;
// win.Attach( hWnd ); // 使用Attach()方法關(guān)聯(lián)
win.CenterWindow(); // 現(xiàn)在可以使用win對象來代替hWnd進(jìn)行操作
win.ShowWindow( nCmdShow );
win.UpdateWindow();
CWindow類也提供了一個HWND操作符,可以把CWindow類的對象轉(zhuǎn)化為窗口句柄,這樣,任何要求使用HWND的地方都可以使用CWindow類的對象代替:
::ShowWindow( win, nCmdShow ); // 此API函數(shù)本來要求HWND類型的參數(shù)
CWindow類使得對窗口的操作更簡單,而且不會增加系統(tǒng)開銷——它經(jīng)過編譯和優(yōu)化后的代碼與使用純API編程的代碼是等價的。
不幸的是,CWindow類不能讓我們自己決定窗口如何響應(yīng)消息。當(dāng)然,我們可以使用CWindow類提供的方法來使一個窗口居中或隱藏,甚至可以向一個窗口發(fā)送消息,但是當(dāng)窗口收到消息后怎么處理則取決于創(chuàng)建這個窗口時使用的窗口類,如果我們是創(chuàng)建的是”button”類的窗口,那么它的表現(xiàn)就象個按鈕,如果用”listbox”類創(chuàng)建,那它就具有跟列表框相同的行為,使用CWindow類我們沒有辦法改變這點(diǎn)。幸好,ATL為我們提供了另外一個類CWindowImpl,它允許我們指定窗口的新行為。
CWindowImpl:CWindowImpl類是從CWindow類派生的,所以我們依然可以使用CWindow類中的成員函數(shù),但是CWindowImpl類的功能更強(qiáng)大,它允許我們指定窗口怎樣處理消息。在傳統(tǒng)的窗口編程中,如果我們要處理窗口消息,我們必須使用窗口函數(shù);但是使用ATL,我們只需要在我們的ATL窗口類中定義一個消息映射。
首先,從CWindowImpl類派生自己的窗口類,如下:
class CMyWindow : public CWindowImpl
{
注意,我們自己的類名必須作為一個模版參數(shù)傳遞給CWindowImpl類。
然后在類的定義里面定義如下的消息映射:
BEGIN_MSG_MAP(CMyWindow)
MESSAGE_HANDLER(WM_PAINT,OnPaint)
MESSAGE_HANDLER(WM_CREATE,OnCreate)
MESSAGE_HANDLER(WM_DESTROY,OnDestroy)
END_MSG_MAP()
下面這句
MESSAGE_HANDLER(WM_PAINT,OnPaint)
的意思是,當(dāng)WM_PAINT消息到達(dá)時,將調(diào)用CMyWindow::OnPaint成員函數(shù)。
最后就是定義處理消息的函數(shù)了,如下:
LRESULT OnPaint(
UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled )
{ ...
}
LRESULT OnCreate(
UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled )
{ ...
}
LRESULT OnDestroy(
UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled )
{ ...
}
}; // CmyWindow
這些函數(shù)中的參數(shù)意義為:第一個是消息ID,中間的兩個參數(shù)的意義取決于消息類型,第四個參數(shù)是一個標(biāo)志,用它來決定這個消息是已經(jīng)處理完了還是需要進(jìn)一步的處理。關(guān)于這些參數(shù),我們在Message Map小結(jié)有更詳細(xì)的討論。
當(dāng)窗口收到一個消息,它將從消息映射表的頂部開始查找匹配的消息處理函數(shù),因此把最常用的消息放在消息映射表的前面是個不錯的注意。如果沒有找到匹配的消息處理函數(shù),則這個消息被發(fā)送到默認(rèn)的窗口過程進(jìn)行處理。
ATL的消息映射表封裝了Windows的消息處理過程,它比傳統(tǒng)的窗口函數(shù)中的大量switch分支或者if語句看起來更加直觀。
要創(chuàng)建一個基于CWindowImpl派生類的窗口,請調(diào)用CWindowImpl類的Create方法:
CMyWindow wnd; // 構(gòu)造一個 CMyWindow 類的對象
wnd.Create( NULL, CWindow::rcDefault, _T("Hello"),
WS_OVERLAPPEDWINDOW|WS_VISIBLE );
注意,CWindowImpl類的Create方法與CWindow類的Create方法略有不同,在CWindow類的Create中,我們必須指定一個注冊了的窗口類,但是CWindowImpl則不同,它創(chuàng)建一個新的窗口類,因此,不需要為它指定窗口類。
一個簡單而完整的示例:
這篇文章中的大部分示例都只是代碼片段,但是下面列出的是一個完整的Hello world的示例程序。雖然我們使用的是ATL,但是沒有涉及到COM,因此在使用Visual C++®建立項目的時候,我們選擇Win32® application而不是ATL COM:
在stdafx.h文件中,加入下面幾行:
#include <atlbase.h>
extern CComModule _Module;
#include <atlwin.h>
在hello.cpp文件中,寫如下代碼:
#include "stdafx.h"
CComModule _Module;
class CMyWindow : public CWindowImpl<CMyWindow> {
BEGIN_MSG_MAP( CMyWindow )
MESSAGE_HANDLER( WM_PAINT, OnPaint )
MESSAGE_HANDLER( WM_DESTROY, OnDestroy )
END_MSG_MAP()
LRESULT OnPaint( UINT, WPARAM, LPARAM, BOOL& ){
PAINTSTRUCT ps;
HDC hDC = GetDC();
BeginPaint( &ps );
TextOut( hDC, 0, 0, _T("Hello world"), 11 );
EndPaint( &ps );
return 0;
}
LRESULT OnDestroy( UINT, WPARAM, LPARAM, BOOL& ){
PostQuitMessage( 0 );
return 0;
}
};
int APIENTRY WinMain( HINSTANCE hInstance, HINSTANCE, LPSTR, int )
{
_Module.Init( NULL, hInstance );
CMyWindow wnd;
wnd.Create( NULL, CWindow::rcDefault, _T("Hello"),
WS_OVERLAPPEDWINDOW|WS_VISIBLE );
MSG msg;
while( GetMessage( &msg, NULL, 0, 0 ) ){
TranslateMessage( &msg );
DispatchMessage( &msg );
}
_Module.Term();
return msg.wParam;
}
在這個示例程序中,CmyWindow是從CWindowImpl派生的,它的消息映射捕獲了兩個消息WM_PAINT和WM_DESTROY,當(dāng)收到WM_PAINT消息時,它的成員函數(shù)OnPaint處理這個消息并在窗口上輸出“Hello world”,當(dāng)收到WM_DESTROY消息時,也就是當(dāng)用戶關(guān)閉這個窗口的時候,調(diào)用OnDestroy函數(shù)處理這個消息,在OnDestroy函數(shù)中調(diào)用PostQuitMessage來結(jié)束消息循環(huán)。
WinMain函數(shù)中創(chuàng)建了一個CmyWindow類的實例并實現(xiàn)了一個標(biāo)準(zhǔn)的消息循環(huán)。(有一些地方,我們必須遵循ATL的規(guī)范,比如在這里我們必須使用_Module。)
消息映射:
有三組用于消息映射的宏,他們分別是:
- 窗口消息映射宏,用于所有的窗口消息(如WM_CREATE、WM_PAINT等);
- 命令消息映射宏,專用于WM_COMMAND消息(比如由控件或菜單發(fā)出的消息);
- 通知消息映射宏,專用于WM_NOTUFY消息(通常由通用控件發(fā)出此消息,比如工具欄控件或列表視圖控件)
窗口消息映射宏:
有兩個窗口消息映射宏,他們分別是:
- MESSAGE_HANDLER
- MESSAGE_RANGE_HANDLER
第一個宏將一個特定的消息映射到相應(yīng)的處理函數(shù);第二個宏將一組消息映射到一個處理函數(shù)。消息處理函數(shù)都要求具有如下的原形:
LRESULT MessageHandler(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
其中,參數(shù)uMsg是消息標(biāo)識,wParam和lParam是兩個附加與消息的參數(shù),(他們的具體意義取決與消息類別。)
消息處理函數(shù)使用bHandled來標(biāo)志消息是否已經(jīng)被完全捕獲,如果bHandled被設(shè)置成FALSE,程序?qū)⒗^續(xù)在消息映射表的后續(xù)部分查找這個消息的其它處理函數(shù)。這個特性使得我們對一個消息使用多個處理函數(shù)成為可能。什么時候需要對一個消息使用多個處理函數(shù)呢?可能是在對多個類鏈接時,也可能是我們只想對一個消息做出響應(yīng)但是并不真正捕獲它。在處理函數(shù)被調(diào)用之前,bHandled被置為TRUE,所以如果我們不在函數(shù)的結(jié)尾顯式地將它置為FALSE,則消息映射表的后續(xù)部分不會被繼續(xù)查找,也不會有其它的處理函數(shù)被調(diào)用。
命令消息映射宏:
命令消息映射宏只處理命令消息(WM_COMMAND消息),但是它能讓我們根據(jù)消息類型或者發(fā)送命令消息的控件ID來指定消息處理函數(shù)。
- COMMAND_HANDLER映射一個特定控件的一條特定消息到一個處理函數(shù);
- COMMAND_ID_HANDLER映射一個特定控件的所有消息到一個處理函數(shù);
- COMMAND_CODE_HANDLER映射任意控件的一個特定消息到一個處理函數(shù);
- COMMAND_RANGE_HANDLER映射一定范圍內(nèi)的控件的所有消息到一個處理函數(shù);
- COMMAND_RANGE_CODE_HANDLER映射一定范圍內(nèi)的控件的一條特定消息到一個處理函數(shù)。
命令消息處理函數(shù)應(yīng)該具有如下的原形:
LRESULT CommandHandler(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled);
其中,參數(shù)wNotifyCode代表消息代碼,wID代表發(fā)送消息的控件的ID,hWndCtl代表發(fā)送消息的控件的窗口句柄,bHandled的意義如前所述。
通知消息映射宏:
通知消息映射宏用來處理通知消息(WM_NOTUFY消息),它根據(jù)通知消息的類型和發(fā)送通知消息的控件的不同將消息映射到不同的處理函數(shù),這些宏與前面講的命令消息映射宏是等價的,唯一的不同就是它處理的是通知消息而不是命令消息。
- NOTIFY_HANDLER
- NOTIFY_ID_HANDLER
- NOTIFY_CODE_HANDLER
- NOTIFY_RANGE_HANDLER
- NOTIFY_RANGE_CODE_HANDLER
通知消息處理函數(shù)都需要如下的原形:
LRESULT NotifyHandler(int idCtrl, LPNMHDR pnmh, BOOL& bHandled);
其中,參數(shù)idCtrl代表發(fā)送通知消息的控件的ID,參數(shù)pnmh是指向一個NMHDR結(jié)構(gòu)的指針,bHandled的意義如前所述。
通知消息包含了一個指向消息細(xì)節(jié)的結(jié)構(gòu)的指針,例如,當(dāng)一個列表視圖控件發(fā)送一個通知消息,這個消息就包含了一個指向NMLVDISPINFO結(jié)構(gòu)的指針,所有類似于NMLVDISPINFO的結(jié)構(gòu)都包含一個NMHDR結(jié)構(gòu)的頭,pnmh就指向這個頭,如果需要訪問這種結(jié)構(gòu)中頭部以外的其它數(shù)據(jù)成員,可以將pnmh轉(zhuǎn)化成相應(yīng)類型的指針。
例如,我們?nèi)绻幚砹斜硪晥D控件發(fā)出的LVN_ENDLABELEDIT通知消息,我們可以把下面這行代碼放到消息映射表中:
NOTIFY_HANDLER( ID_LISTVIEW, LVN_ENDLABELEDIT, OnEndLabelEdit)
這個通知消息附帶的額外信息包含在一個NMLVDISPINFO結(jié)構(gòu)中,因此,消息處理函數(shù)看起來應(yīng)該象下面這個樣子:
LRESULT OnEndLabelEdit(int idCtrl, LPNMHDR pnmh, BOOL& bHandled)
{
// The item is -1 if editing is being canceled.
if ( ((NMLVDISPINFO*)pnmh)->item.iItem == -1) return FALSE;
...
可以看出,pnmh指針被轉(zhuǎn)化成NMLVDISPINFO*類型,以便訪問頭部結(jié)構(gòu)以外的數(shù)據(jù)。
為現(xiàn)有的窗口類添加功能:有許多向現(xiàn)有的窗口添加功能的方法。如果這個類是ATL窗口類,我們可以從這個窗口類派生自己的類,就象Base Class Chaining中描述的一樣。這種方法主要是一個C++類的繼承加上一點(diǎn)消息映射的鏈接。
如果我們想擴(kuò)展一個預(yù)定義的窗口類(如按紐類或列表框類)的功能,我們可以超類化它。就是創(chuàng)建一個基于這個預(yù)定義類的新類,并在消息映射表中添加消息映射以增強(qiáng)它的功能。
有些時候,我們需要改變一個已經(jīng)存在的窗口實例的行為,而不是一個窗口類——或許我們要讓一個對話框上的編輯框做點(diǎn)什么特別的事情。在這種情況下,我們可以寫一個新的ATL窗口類,并子類化這個已經(jīng)存在的編輯框。任何本該發(fā)送到這個編輯框的消息都會先被發(fā)送到這個子類的對象。
另外一種可選的方法:我們也可以讓這個編輯框成為一個被包含的窗口,所有發(fā)送到這個編輯框的消息都會經(jīng)過它的容器窗口;我們可以在這個容器窗口中為這個被包含的窗口實現(xiàn)特殊的消息處理。
最后的一種方法就是消息反射,當(dāng)一個窗口收到一個消息后不處理它,而是反射給發(fā)送這個消息的窗口自己處理,這種技術(shù)可以用來創(chuàng)建自包含的控件。
基類消息鏈(Base Class Chaining):如果我們已經(jīng)有一些實現(xiàn)了特定功能的ATL窗口類,我們可以從它們派生新類以充分利用繼承的優(yōu)點(diǎn)。比如:
class CBase: public CWindowImpl< CBase >
// simple base window class: shuts down app when closed
{
BEGIN_MSG_MAP( CBase )
MESSAGE_HANDLER( WM_DESTROY, OnDestroy )
END_MSG_MAP()
LRESULT OnDestroy( UINT, WPARAM, LPARAM, BOOL& )
{
PostQuitMessage( 0 );
return 0;
}
};
class CDerived: public CBase
// derived from CBase; handles mouse button events
{
BEGIN_MSG_MAP( CDerived )
MESSAGE_HANDLER( WM_LBUTTONDOWN, OnButtonDown )
END_MSG_MAP()
LRESULT OnButtonDown( UINT, WPARAM, LPARAM, BOOL& )
{
ATLTRACE( "button down\n" );
return 0;
}
};
// in WinMain():
...
CDerived win;
win.Create( NULL, CWindow::rcDefault, "derived window" );
可是,上面的代碼有一個問題。當(dāng)我們在調(diào)試模式下運(yùn)行這個程序,一個窗口出現(xiàn)了,如果我們在這個窗口中單擊,“button down”將出現(xiàn)在輸出窗口中,這是CDrived類的功能,可是,當(dāng)我們關(guān)閉這個窗口的時候,程序并不退出,盡管CBase類處理了WM_DESTROY消息并且CDrived類是從CBase類派生的。
Why?因為我們必須明確地將一個消息映射表鏈接到另外一個。如下:
BEGIN_MSG_MAP( CDerived )
MESSAGE_HANDLER( WM_LBUTTONDOWN, OnButtonDown )
CHAIN_MSG_MAP( CBase ) // 鏈接到基類
END_MSG_MAP()
現(xiàn)在,任何在CDrived類中沒有被處理的消息都會被傳到CBase類中。
為什么不自動將派生類的消息映射和它的基類的消息映射鏈接起來呢?這是因為在ATL的體系結(jié)構(gòu)中有很多多重繼承的情況,這種情況下沒有辦法知道究竟應(yīng)該鏈接到哪個基類,所以只好讓程序員自己來做決定。
可選的消息映射:
消息映射鏈允許多個類同時進(jìn)行消息處理,同時也帶來了問題:如果我們在多個類中都要響應(yīng)WM_CREATE消息,但是不同的類需要基類提供不同的處理,怎么辦呢?為了解決這個問題,ATL使用了可選的消息映射:將消息映射表分成很多節(jié),每一節(jié)用不同的數(shù)字標(biāo)識,每一節(jié)都是一個可選的消息映射表。
// in class CBase:
BEGIN_MSG_MAP( CBase )
MESSAGE_HANDLER( WM_CREATE, OnCreate1 )
MESSAGE_HANDLER( WM_PAINT, OnPaint1 )
ALT_MSG_MAP( 100 )
MESSAGE_HANDLER( WM_CREATE, OnCreate2 )
MESSAGE_HANDLER( WM_PAINT, OnPaint2 )
ALT_MSG_MAP( 101)
MESSAGE_HANDLER( WM_CREATE, OnCreate3 )
MESSAGE_HANDLER( WM_PAINT, OnPaint3 )
END_MSG_MAP()
如上,基類的消息映射表由3節(jié)組成:一個默認(rèn)的消息映射表(隱含的標(biāo)識為0)和兩個可選的消息映射表(標(biāo)識為100和101)。
當(dāng)你鏈接消息映射表時,指定你所希望的方案的標(biāo)識,如下:
class CDerived: public CBase {
BEGIN_MSG_MAP( CDerived )
CHAIN_MSG_MAP_ALT( CBase, 100 )
END_MSG_MAP()
...
CDrived類的消息映射表鏈接到CBase類中標(biāo)識號為100的可選節(jié),因此當(dāng)WM_PAINT到達(dá)時,CBase::OnPaint2被調(diào)用。
(譯者注:我覺得這種方法不太合乎C++的思想,基類的編寫者不一定總能知道派生自它的類會有哪些需求,而且把所有不同的版本都在基類中實現(xiàn),基類中無用的代碼量會大大增加。更好的辦法應(yīng)該是把基類中的消息處理函數(shù)聲明為虛函數(shù)。總之,我覺得這一小節(jié)并不能體現(xiàn)出可選消息映射的真正用途。)
其它類型的鏈:
除了基類消息映射鏈,ATL也提供了成員鏈(member chaining)和動態(tài)鏈(dynamic chaining),這些很少使用到的鏈技術(shù)超出了我們這篇文章的討論范圍,但是可以簡單提一下。成員鏈允許把消息映射鏈接到一個類的成員變量,動態(tài)鏈允許在運(yùn)行時進(jìn)行動態(tài)鏈接。如果你想了解更多,請參考ATL文檔中的CHAIN_MSG_MAP_DYNAMIC 和CHAIN_MSG_MAP_MEMBER的相關(guān)內(nèi)容。
窗口的超類化:超類化定義一個類,并為預(yù)定義的窗口類(如按鈕類或列表框類)添加新的功能,下面的例子超類化一個按鈕,讓這個按鈕在被單擊的時候發(fā)出蜂鳴。
class CBeepButton: public CWindowImpl< CBeepButton >
{
public:
DECLARE_WND_SUPERCLASS( _T("BeepButton"), _T("Button") )
BEGIN_MSG_MAP( CBeepButton )
MESSAGE_HANDLER( WM_LBUTTONDOWN, OnLButtonDown )
END_MSG_MAP()
LRESULT OnLButtonDown( UINT, WPARAM, LPARAM, BOOL& bHandled )
{
MessageBeep( MB_ICONASTERISK );
bHandled = FALSE; // alternatively: DefWindowProc()
return 0;
}
}; // CBeepButton
DECLARE_WND_SUPERCLASS宏聲明了這個窗口的類名(“BeepButton”)和被超類化的類名(“Button”)。它的消息映射表只有一個入口項,將WM_LBUTTONDOWN消息映射到OnLButtonDown函數(shù)。其余的消息都讓默認(rèn)的窗口過程處理,除了可以發(fā)出蜂鳴外,CbeepButton需要和其它的按鈕表現(xiàn)相同,因此在OnLButtonDown函數(shù)的最后,需要將bHandled設(shè)置為FALSE,讓默認(rèn)的窗口過程在OnLButtonDown函數(shù)完成后對WM_LBUTTONDOWN消息進(jìn)行其它的處理。(另外的一種方法是直接調(diào)用DefWindowProc函數(shù)。)
到目前為止,我們所做的只是定義了一個新類;我們依然需要創(chuàng)建一些真正的CbeepButton窗口,下面的類定義了兩個CbeepButton類型的成員變量,因此,當(dāng)這個類的窗口被創(chuàng)建時,將會創(chuàng)建兩個CbeepButton類型的子窗口。
const int ID_BUTTON1 = 101;
const int ID_BUTTON2 = 102;
class CMyWindow: public CWindowImpl< CMyWindow, CWindow,
CWinTraits<WS_OVERLAPPEDWINDOW|WS_VISIBLE> >
{
CBeepButton b1, b2;
BEGIN_MSG_MAP( CMyWindow )
MESSAGE_HANDLER( WM_CREATE, OnCreate )
COMMAND_CODE_HANDLER( BN_CLICKED, onClick )
END_MSG_MAP()
LRESULT onClick(WORD wNotifyCode, WORD wID, HWND hWndCtl,
BOOL& bHandled)
{
ATLTRACE( "Control %d clicked\n", wID );
return 0;
}
LRESULT OnCreate( UINT, WPARAM, LPARAM, BOOL& )
{
RECT r1 = { 10, 10, 250, 80 };
b1.Create(*this, r1, "beep1", WS_CHILD|WS_VISIBLE, 0, ID_BUTTON1);
RECT r2 = { 10, 110, 250, 180 };
b2.Create(*this, r2, "beep2", WS_CHILD|WS_VISIBLE, 0, ID_BUTTON2);
return 0;
}
}; // CMyWindow
窗口的子類化:子類化允許我們改變一個已經(jīng)存在的窗口的行為,我們經(jīng)常用它來改變控件的行為。它的實現(xiàn)機(jī)制是插入一個消息映射表來截取發(fā)向控件的消息。舉例說明:假設(shè)有一個對話框,對話框上有一個編輯框控件,我們想讓這個控件只接受不是數(shù)字的字符。我們可以截獲發(fā)往這個控件的WM_CHAR消息并拋棄接收到的數(shù)字字符。下面的類實現(xiàn)這個功能:
class CNoNumEdit: public CWindowImpl< CNoNumEdit >
{
BEGIN_MSG_MAP( CNoNumEdit )
MESSAGE_HANDLER( WM_CHAR, OnChar )
END_MSG_MAP()
LRESULT OnChar( UINT, WPARAM wParam, LPARAM, BOOL& bHandled )
{
TCHAR ch = wParam;
if( _T(''0'') <= ch && ch <= _T(''9'') )
MessageBeep( 0 );
else
bHandled = FALSE;
return 0;
}
};
這個類只處理一個消息WM_CHAR,如果這個字符是數(shù)字的話,則調(diào)用MessageBeep( 0 )并返回,這樣可以有效地忽略這個字符。如果不是數(shù)字,則將bHandled設(shè)置為FALSE,指明默認(rèn)的窗口過程這個消息需要進(jìn)一步處理。
現(xiàn)在我們將子類化一個編輯框控件,以便CnoNumEdit能夠搶先處理發(fā)到這個編輯框得消息。(下面得例子用到了CdialogImpl類,這個類我們將在ATL中的對話框類一節(jié)中介紹。)在這個例子中,CmyDialog類中用到了一個對話框資源(ID號為IDD_DIALOG1),對話框中有一個編輯框控件(ID號為IDC_EDIT1),當(dāng)對話框初始化的時候,編輯框經(jīng)過SubclassWindow而變成一個不接受數(shù)字的編輯框:
class CMyDialog: public CDialogImpl<CMyDialog>
{
public:
enum { IDD = IDD_DIALOG1 };
BEGIN_MSG_MAP( CMyDialog )
MESSAGE_HANDLER( WM_INITDIALOG, OnInitDialog )
END_MSG_MAP()
LRESULT OnInitDialog( UINT, WPARAM, LPARAM, BOOL& )
{
ed.SubclassWindow( GetDlgItem( IDC_EDIT1 ) );
return 0;
}
CNoNumEdit ed;
};