被包含的窗口:
一個被包含的窗口是一個不響應任何消息的窗口,它將收到的所有消息重新發送到另外一個窗口的消息映射,這個另外的窗口就是它的容器窗口。通常情況下,被包含的窗口是它的容器窗口的子窗口,但情況并不是總是這樣。容器窗口并不是必須等同于父窗口,包含與被包含的關系取決于C++類,被包含的窗口是容器窗口類的一個數據成員,而父窗口和子窗口的關系體現在屏幕上,它們的關系是創建窗口時確定的。
一個被包含的窗口建立在已注冊的窗口類的基礎之上,比如編輯框控件。如果一個編輯框被包含,那么發送到它的消息實際上被它的容器窗口的消息映射處理。使用這種方法,可以改變編輯框控件的標準行為。這有點類似于子類化但是不需要定義新類來子類化控件。和前面那個定義CnoNumEdit類響應WM_CHAR消息的例子相比,處理WM_CHAR消息的容器窗口類看起來如下:
class CMyWindow: public CWindowImpl
{
CContainedWindow m_contained;
public:
CMyWindow(): m_contained( _T("edit"), this, 99 )
{
}
...
CmyWindow是一個容器窗口類,它的構造函數對CcontainedWindow類型的成員做這樣的初始化:被包含的窗口是編輯框,發送它的消息到“this”(它的父窗口),使用可選消息映射表99。
BEGIN_MSG_MAP( CMyWindow )
MESSAGE_HANDLER( WM_CREATE, OnCreate )
MESSAGE_HANDLER( WM_DESTROY, OnDestroy )
ALT_MSG_MAP( 99 ) // contained window''s messages come here...
MESSAGE_HANDLER( WM_CHAR, OnChar )
END_MSG_MAP()
當父窗口被創建的時候,被包含的窗口也被創建(在WM_CREATE消息的響應函數中)。因為被包含的控件是以編輯框為基礎的,所以它在屏幕上看起來象一個編輯框:
LRESULT OnCreate( UINT, WPARAM, LPARAM, BOOL& )
{
RECT rc = { 10, 10, 200, 35 };
m_contained.Create( *this, rc, _T("non-numeric edit"),
WS_CHILD|WS_VISIBLE|WS_BORDER, 0, 666 );
return 0;
}
在這個例子中,容器窗口同時也是被包含窗口的父窗口。
當被包含的窗口收到WM_CHAR消息時,容器窗口的OnChar成員函數被調用。這個函數和前面的CnoNumEdit例子中的相同,但是在這個例子中,它時容器類的成員函數。
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;
}
LRESULT OnDestroy( UINT, WPARAM, LPARAM, BOOL& )
{
PostQuitMessage( 0 );
return 0;
}
};
我們同樣也可以用被包含的窗口來子類化對話框中已經存在的控件,和正規的子類化不同,被子類化的窗口的消息時被容器窗口捕獲的。在下面的例子中,一個對話框子類化了一個編輯框控件,把它轉化成了被包含的窗口;那個對話框(容器)捕獲WM_CHAR消息并忽略掉數字字符,然后在發送到編輯框控件。(CdialogImpl在ATL中的對話框類一節講述。)
class CMyDialog: public CDialogImpl<CMyDialog>
{
public:
enum { IDD = IDD_DIALOG1 };
// contained window is an edit control:
CMyDialog(): m_contained( "edit", this, 123 )
{
}
BEGIN_MSG_MAP( CMyDialog )
MESSAGE_HANDLER( WM_INITDIALOG, OnInitDialog )
ALT_MSG_MAP( 123 ) // contained window''s messages come here...
MESSAGE_HANDLER( WM_CHAR, OnChar )
END_MSG_MAP()
LRESULT OnInitDialog( UINT, WPARAM, LPARAM, BOOL& bHandled )
{
// when the dialog box is created, subclass its edit control:
m_contained.SubclassWindow( GetDlgItem(IDC_EDIT1) );
bHandled = FALSE;
return 0;
}
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;
}
CContainedWindow m_contained;
};
消息反射:前面講述了一些擴展窗口功能的方法,這些方法是通過使窗口響應發往窗口的消息實現的。和前面的方法相反,消息反射是使窗口能夠響應從它們自己發出的消息。
當用戶和控件交互的時候,控件通常使發送一個WM_COMMAND或者WM_NOTIFY消息給它的父窗口;然后父窗口做出響應,比如:
class CParentWindow: CWindowImpl<CParentWindow>
{
// 假設這個窗口有一個按鈕型的子窗口,
// 并且其 ID 為 ID_BUTTON
BEGIN_MSG_MAP( CParentWindow )
COMMAND_ID_HANDLER( ID_BUTTON, OnButton )
MESSAGE_HANDLER( WM_CTLCOLORBUTTON, OnColorButton )
...
當按鈕被按下的時候,它發送一個命令消息給父窗口,然后CParentWindow::OnButton被調用。同理,當按鈕需要被繪制的時候,它發送WM_CTLCOLORBUTTON消息給父窗口,CParentWindow::OnColorButton響應這個消息,它使用特定的畫刷繪制控件。
某些情況下,讓控件自己響應它發送出去的消息比讓父窗口響應要好得多。ATL提供了消息反射的機制:當控件向父窗口發送消息的時候,父窗口能夠將消息反射給控件。
class CParentWindow: CWindowImpl
{
BEGIN_MSG_MAP( CParentWindow )
MESSAGE_HANDLER( WM_CREATE, OnCreate )
MESSAGE_HANDLER( WM_DESTROY, OnDestroy )
...other messages that CParentWindow will handle...
REFLECT_NOTIFICATIONS()
END_MSG_MAP()
...
當父窗口收到一個消息,先查找它的消息映射表,如果沒有和這個消息相匹配的入口,則REFLECT_NOTIFICATIONS宏使得該消息被反射給發送這個消息的控件。控件可以提供響應反射消息的處理函數,如下:
class CHandlesItsOwnMessages: CWindowImpl<CHandlesItsOwnMessage>
{
public:
DECLARE_WND_SUPERCLASS( _T("Superbutton"), _T("button") )
BEGIN_MSG_MAP( CHandlesItsOwnMessage )
MESSAGE_HANDLER( OCM_COMMAND, OnCommand )
MESSAGE_HANDLER( OCM_CTLCOLORBUTTON, OnColorButton )
DEFAULT_REFLECTION_HANDLER()
END_MSG_MAP()
...
注意,反射消息的消息標志以OCM_開頭,而不是WM_。這可以讓你區分這個消息究竟是否是被反射回來的。
這個控件要么是這個類的實例,要么是一個被子類化的按鈕控件。例如:
// in CParentWindow:
CHandlesItsOwnMessages m_button;
LRESULT OnCreate( UINT, WPARAM, LPARAM, BOOL& )
{
RECT rc; // initialize appropriately
m_button.Create( *this, rc, _T("click me"), WS_CHILD|WS_VISIBLE );
...
或者,如果這個按鈕控件是已存在的(例如,父窗口是一個對話框):
m_button.SubclassWindow( GetDlgItem(ID_BUTTON) );
下面的例子定義了一個CstaticLink類,它是一個Static控件,當點擊它的時候,將打開一個指定的網頁。所有從CstaticLink發送出去的消息都被它的父窗口反射回來(在這個例子中,用到對話框,請看ATL中的對話框類這一節)。除了響應反射回的命令消息,CstaticLink還處理反射回的WM_CTLCOLORSTATIC消息以便它能夠讓自己在點擊前和點擊后顯示不同的顏色。
#include "stdafx.h"
#include "resource.h"
CComModule _Module;
class CStaticLink : public CWindowImpl<CStaticLink> {
/*
Based on CStaticLink by Paul DiLascia, C++ Q&A, Microsoft Systems
Journal 12/1997.
Turns static controls into clickable "links" -- when the control is
clicked, the file/program/webpage named in the control''s text (or
set by SetLinkText()) is opened via ShellExecute(). Static control
can be either text or graphic (bitmap, icon, etc.).
*/
public:
DECLARE_WND_SUPERCLASS( _T("StaticLink"), _T("Static") )
CStaticLink() :
m_colorUnvisited( RGB(0,0,255) ),
m_colorVisited( RGB(128,0,128) ),
m_bVisited( FALSE ),
m_hFont( NULL )
{
}
void SetLinkText( LPCTSTR szLink ) {
USES_CONVERSION;
m_bstrLink = T2OLE( szLink );
}
BEGIN_MSG_MAP(CStaticLink)
// uses message reflection: WM_* comes back as OCM_*
MESSAGE_HANDLER( OCM_COMMAND, OnCommand )
MESSAGE_HANDLER( OCM_CTLCOLORSTATIC, OnCtlColor )
MESSAGE_HANDLER( WM_DESTROY, OnDestroy ) // not a reflected message
DEFAULT_REFLECTION_HANDLER()
END_MSG_MAP()
LRESULT OnDestroy( UINT, WPARAM, LPARAM, BOOL& ) {
if( m_hFont ) DeleteObject( m_hFont );
return 0;
}
LRESULT OnCommand( UINT, WPARAM wParam, LPARAM, BOOL& ) {
USES_CONVERSION;
int code = HIWORD( wParam );
if( code == STN_CLICKED || code == STN_DBLCLK ){
if( m_bstrLink.Length() == 0 ){
GetWindowText( &m_bstrLink );
}
if( (int)ShellExecute( *this, _T("open"),
OLE2T(m_bstrLink), NULL, NULL, SW_SHOWNORMAL ) > 32 ){
m_bVisited = TRUE; // return codes > 32 => success
Invalidate();
}else{
MessageBeep( 0 );
ATLTRACE( _T("Error: CStaticLink couldn''t open file") );
}
}
return 0;
}
LRESULT OnCtlColor( UINT, WPARAM wParam, LPARAM, BOOL& ) {
// notify bit must be set to get STN_* notifications
ModifyStyle( 0, SS_NOTIFY );
HBRUSH hBr = NULL;
if( (GetStyle() & 0xff) <= SS_RIGHT ){
// it''s a text control: set up font and colors
if( !m_hFont ){
LOGFONT lf;
GetObject( GetFont(), sizeof(lf), &lf );
lf.lfUnderline = TRUE;
m_hFont = CreateFontIndirect( &lf );
}
HDC hDC = (HDC)wParam;
SelectObject( hDC, m_hFont );
SetTextColor( hDC, m_bVisited ? m_colorVisited
: m_colorUnvisited );
SetBkMode( hDC, TRANSPARENT );
hBr = (HBRUSH)GetStockObject( HOLLOW_BRUSH );
}
return (LRESULT)hBr;
}
private:
COLORREF m_colorUnvisited;
COLORREF m_colorVisited;
BOOL m_bVisited;
HFONT m_hFont;
CComBSTR m_bstrLink;
}; // CStaticLink
class CReflectDlg : public CDialogImpl<CReflectDlg> {
public:
enum { IDD = IDD_DIALOG1 };
BEGIN_MSG_MAP(CReflectDlg)
COMMAND_RANGE_HANDLER( IDOK, IDCANCEL, OnClose )
MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog)
REFLECT_NOTIFICATIONS() // reflect messages back to static links
END_MSG_MAP()
LRESULT OnInitDialog(UINT, WPARAM, LPARAM, BOOL&)
{
CenterWindow( GetParent() );
// a textual static control:
s1.SubclassWindow( GetDlgItem(IDS_TEST1) );
// a static control displaying an icon
s2.SubclassWindow( GetDlgItem(IDS_TEST2) );
// set the icon''s link
s2.SetLinkText( _T("http://www.microsoft.com") );
return 1;
}
LRESULT OnClose(UINT, WPARAM wID, HWND, BOOL& )
{
::EndDialog( m_hWnd, wID );
return 0;
}
private:
CStaticLink s1, s2;
}; // CReflectDlg
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR, int nCmdShow)
{
_Module.Init( NULL, hInstance );
CReflectDlg dlg;
dlg.DoModal();
_Module.Term();
return 0;
}