分类: C/C++
2008-08-05 13:56:15
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();我们应该在我们的大脑中我们应该保持这样一个概念:ATL的窗口对象与Windows系统中的窗口是不同的。Windows系统中的窗口指的是操作系统中维持的一块数据,操作系统靠这块数据来操作屏幕上的一块区域。而一个ATL窗口对象,是CWindow类的一个实例,它是一个C 对象,它的内部没有保存任何有关屏幕区域或者窗口数据结构的内容,只保存了一个窗口的句柄,这个句柄保存在它的数据成员m_hWnd中,CWindow对象和它在屏幕上显示出来的窗口就是靠这个句柄联系起来的。
CWindow win;然后创建它的窗口:
win.Create( "button", NULL, CWindow::rcDefault, "Click me", WS_CHILD );我们也可以构造一个CWindow对象,然后把它和一个已经存在的窗口关联起来,这样我们就可以通过CWindow类的成员函数来操作这个已经存在的窗口。这种方法非常有用,因为CWindow类提供的函数都是封装好了的,用起来很方便,比如CWindow类中的CenterWindow, GetDescendantWindow等函数用起来就比直接使用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 ); // 通过构造函数关联 // 或 // CWindow win; // win = hWnd; // 通过赋值操作符关联 // 或 // CWindow win; // win.Attach( hWnd ); // 使用Attach()方法关联 win.CenterWindow(); // 现在可以使用win对象来代替hWnd进行操作 win.ShowWindow( nCmdShow ); win.UpdateWindow();CWindow类也提供了一个HWND操作符,可以把CWindow类的对象转化为窗口句柄,这样,任何要求使用HWND的地方都可以使用CWindow类的对象代替:
::ShowWindow( win, nCmdShow ); // 此API函数本来要求HWND类型的参数CWindow类使得对窗口的操作更简单,而且不会增加系统开销——它经过编译和优化后的代码与使用纯API编程的代码是等价的。
class CMyWindow : public CWindowImpl注意,我们自己的类名必须作为一个模版参数传递给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)的意思是,当WM_PAINT消息到达时,将调用CMyWindow::OnPaint成员函数。
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这些函数中的参数意义为:第一个是消息ID,中间的两个参数的意义取决于消息类型,第四个参数是一个标志,用它来决定这个消息是已经处理完了还是需要进一步的处理。关于这些参数,我们在Message Map小结有更详细的讨论。
CMyWindow wnd; // 构造一个 CMyWindow 类的对象 wnd.Create( NULL, CWindow::rcDefault, _T("Hello"), WS_OVERLAPPEDWINDOW|WS_VISIBLE );注意,CWindowImpl类的Create方法与CWindow类的Create方法略有不同,在CWindow类的Create中,我们必须指定一个注册了的窗口类,但是CWindowImpl则不同,它创建一个新的窗口类,因此,不需要为它指定窗口类。
#include在hello.cpp文件中,写如下代码:extern CComModule _Module; #include
#include "stdafx.h" CComModule _Module; class CMyWindow : public CWindowImpl{ 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,当收到WM_PAINT消息时,它的成员函数OnPaint处理这个消息并在窗口上输出“Hello world”,当收到WM_DESTROY消息时,也就是当用户关闭这个窗口的时候,调用OnDestroy函数处理这个消息,在OnDestroy函数中调用PostQuitMessage来结束消息循环。
WinMain函数中创建了一个CmyWindow类的实例并实现了一个标准的消息循环。(有一些地方,我们必须遵循ATL的规范,比如在这里我们必须使用_Module。)
消息映射:
有三组用于消息映射的宏,他们分别是:
窗口消息映射宏:
有两个窗口消息映射宏,他们分别是:
第一个宏将一个特定的消息映射到相应的处理函数;第二个宏将一组消息映射到一个处理函数。消息处理函数都要求具有如下的原形:
LRESULT MessageHandler(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
其中,参数uMsg是消息标识,wParam和lParam是两个附加与消息的参数,(他们的具体意义取决与消息类别。)
消息处理函数使用bHandled来标志消息是否已经被完全捕获,如果bHandled被设置成FALSE,程序将继续在消息映射表的后续部分查找这个消息的其它处理函数。这个特性使得我们对一个消息使用多个处理函数成为可能。什么时候需要对一个消息使用多个处理函数呢?可能是在对多个类链接时,也可能是我们只想对一个消息做出响应但是并不真正捕获它。在处理函数被调用之前,bHandled被置为TRUE,所以如果我们不在函数的结尾显式地将它置为FALSE,则消息映射表的后续部分不会被继续查找,也不会有其它的处理函数被调用。
命令消息映射宏:
命令消息映射宏只处理命令消息(WM_COMMAND消息),但是它能让我们根据消息类型或者发送命令消息的控件ID来指定消息处理函数。
命令消息处理函数应该具有如下的原形:
LRESULT CommandHandler(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled);
其中,参数wNotifyCode代表消息代码,wID代表发送消息的控件的ID,hWndCtl代表发送消息的控件的窗口句柄,bHandled的意义如前所述。
通知消息映射宏:
通知消息映射宏用来处理通知消息(WM_NOTUFY消息),它根据通知消息的类型和发送通知消息的控件的不同将消息映射到不同的处理函数,这些宏与前面讲的命令消息映射宏是等价的,唯一的不同就是它处理的是通知消息而不是命令消息。
通知消息处理函数都需要如下的原形:
LRESULT NotifyHandler(int idCtrl, LPNMHDR pnmh, BOOL& bHandled);其中,参数idCtrl代表发送通知消息的控件的ID,参数pnmh是指向一个NMHDR结构的指针,bHandled的意义如前所述。
NOTIFY_HANDLER( ID_LISTVIEW, LVN_ENDLABELEDIT, OnEndLabelEdit)这个通知消息附带的额外信息包含在一个NMLVDISPINFO结构中,因此,消息处理函数看起来应该象下面这个样子:
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指针被转化成NMLVDISPINFO*类型,以便访问头部结构以外的数据。
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" );可是,上面的代码有一个问题。当我们在调试模式下运行这个程序,一个窗口出现了,如果我们在这个窗口中单击,“button down”将出现在输出窗口中,这是CDrived类的功能,可是,当我们关闭这个窗口的时候,程序并不退出,尽管CBase类处理了WM_DESTROY消息并且CDrived类是从CBase类派生的。
BEGIN_MSG_MAP( CDerived ) MESSAGE_HANDLER( WM_LBUTTONDOWN, OnButtonDown ) CHAIN_MSG_MAP( CBase ) // 链接到基类 END_MSG_MAP()现在,任何在CDrived类中没有被处理的消息都会被传到CBase类中。
// 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节组成:一个默认的消息映射表(隐含的标识为0)和两个可选的消息映射表(标识为100和101)。
class CDerived: public CBase { BEGIN_MSG_MAP( CDerived ) CHAIN_MSG_MAP_ALT( CBase, 100 ) END_MSG_MAP() ...CDrived类的消息映射表链接到CBase类中标识号为100的可选节,因此当WM_PAINT到达时,CBase::OnPaint2被调用。
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; } }; // CBeepButtonDECLARE_WND_SUPERCLASS宏声明了这个窗口的类名(“BeepButton”)和被超类化的类名(“Button”)。它的消息映射表只有一个入口项,将WM_LBUTTONDOWN消息映射到OnLButtonDown函数。其余的消息都让默认的窗口过程处理,除了可以发出蜂鸣外,CbeepButton需要和其它的按钮表现相同,因此在OnLButtonDown函数的最后,需要将bHandled设置为FALSE,让默认的窗口过程在OnLButtonDown函数完成后对WM_LBUTTONDOWN消息进行其它的处理。(另外的一种方法是直接调用DefWindowProc函数。)
const int ID_BUTTON1 = 101; const int ID_BUTTON2 = 102; class CMyWindow: public CWindowImpl< CMyWindow, CWindow, CWinTraits窗口的子类化:> { 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
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,如果这个字符是数字的话,则调用MessageBeep( 0 )并返回,这样可以有效地忽略这个字符。如果不是数字,则将bHandled设置为FALSE,指明默认的窗口过程这个消息需要进一步处理。
class CMyDialog: public CDialogImpl被包含的窗口:{ 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; };
class CMyWindow: public CWindowImplCmyWindow是一个容器窗口类,它的构造函数对CcontainedWindow类型的成员做这样的初始化:被包含的窗口是编辑框,发送它的消息到“this”(它的父窗口),使用可选消息映射表99。{ CContainedWindow m_contained; public: CMyWindow(): m_contained( _T("edit"), 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; }在这个例子中,容器窗口同时也是被包含窗口的父窗口。
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消息反射:{ 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; };
class CParentWindow: CWindowImpl当按钮被按下的时候,它发送一个命令消息给父窗口,然后CParentWindow::OnButton被调用。同理,当按钮需要被绘制的时候,它发送WM_CTLCOLORBUTTON消息给父窗口,CParentWindow::OnColorButton响应这个消息,它使用特定的画刷绘制控件。{ // 假设这个窗口有一个按钮型的子窗口, // 并且其 ID 为 ID_BUTTON BEGIN_MSG_MAP( CParentWindow ) COMMAND_ID_HANDLER( ID_BUTTON, OnButton ) MESSAGE_HANDLER( WM_CTLCOLORBUTTON, OnColorButton ) ...
class CParentWindow: CWindowImpl当父窗口收到一个消息,先查找它的消息映射表,如果没有和这个消息相匹配的入口,则REFLECT_NOTIFICATIONS宏使得该消息被反射给发送这个消息的控件。控件可以提供响应反射消息的处理函数,如下:{ 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() ...
class CHandlesItsOwnMessages: CWindowImpl注意,反射消息的消息标志以OCM_开头,而不是WM_。这可以让你区分这个消息究竟是否是被反射回来的。{ 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() ...
// 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 CWindowImplATL中的对话框类:{ /* 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 { 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("") ); 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; }
BEGIN_MSG_MAP( CMyMainWindow ) COMMAND_ID_HANDLER( ID_HELP_ABOUT, OnHelpAbout ) ... LRESULT OnHelpAbout( WORD, WORD, HWND, BOOL& ) { CSimpleDialog我们可以看到对话框资源的ID(IDD_DIALOG1)被作为一个模版参数传递给CSimpleDialog类,DoModal方法显示对话框。当用户点击OK按钮时,CSimpleDialog类关闭对话框并返回按钮的ID。(CSimpleDialog类实现了对按钮IDOK,IDCANCEL,IDABORT,IDRETRY,IDIGNORE,IDYES和IDNO的响应。)dlg; int ret = dlg.DoModal(); return 0; }
class CMyModelessDialog: public CDialogImpl和CSimpleDialog不同,我们不需要将对话框资源的ID作为模板参数传递给它,但是我们必须将这个类和对话框资源联系起来,我们通过在类中定义一个枚举变量实现:{
public: enum { IDD = IDD_DIALOG1 };然后定义消息映射表:
BEGIN_MSG_MAP( CMyDialog ) MESSAGE_HANDLER( WM_INITDIALOG, OnInitDialog ) MESSAGE_HANDLER( WM_CLOSE, OnClose ) ... END_MSG_MAP()响应函数的定义和前面的一样,但是有一点需要注意,如果你实现的是一个非模式对话框,那么在WM_CLOSE消息的响应函数中必须调用DestroyWindow:
LRESULT OnClose( UINT, WPARAM, LPARAM, BOOL& ) { DestroyWindow(); return 0; } ... }; // CMyModelessDialog要在屏幕上创建这样一个对话框,需要创建这个类的一个实例并调用Create方法:
CMyModelessDialog dlg; dlg.Create( wndParent );如果对话框资源没有选中WS_VISIBLE属性,我们需要这样让对话框显示出来:
dlg.ShowWindow( SW_SHOW );下面的例子有一个非模式的对话框可以接受用户输入的字符串,然后在主窗口中显示这个字符串。对话框中有一个编辑框控件和一个按钮;当按钮被点击时,对话框调用它所属窗口的DoSomething方法对编辑框中的字符串进行处理,它所属的窗口是一个超类化的列表框控件,DoSomething方法的功能是将字符串添加到列表框中。
#include "atlbase.h" CComModule _Module; #include "atlwin.h" #include "resource.h" class CMyWindow: public CWindowImpl指定窗口类的信息:{ public: DECLARE_WND_SUPERCLASS( "MyWindow", "listbox" ) BEGIN_MSG_MAP( CMyWindow ) MESSAGE_HANDLER( WM_DESTROY, OnDestroy ) END_MSG_MAP() LRESULT OnDestroy( UINT, WPARAM, LPARAM, BOOL& ) { PostQuitMessage( 0 ); return 0; } void DoSomething( LPCTSTR s ) { SendMessage( LB_ADDSTRING, 0, reinterpret_cast (s) ); } }; class CMyDialog: public CDialogImpl { public: enum { IDD = IDD_DIALOG1 }; BEGIN_MSG_MAP( CMyDialog ) COMMAND_ID_HANDLER( IDC_BUTTON1, OnButton ) MESSAGE_HANDLER( WM_INITDIALOG, OnInitDialog ) MESSAGE_HANDLER( WM_CLOSE, OnClose ) END_MSG_MAP() LRESULT OnButton(WORD, WORD, HWND, BOOL&) { char buf[100]; m_ed.GetWindowText( buf, 100 ); m_owner.DoSomething( buf ); return 0; } LRESULT OnInitDialog( UINT, WPARAM, LPARAM, BOOL& ) { m_owner.Attach( GetParent() ); CenterWindow( m_owner ); m_ed = GetDlgItem( IDC_EDIT1 ); return TRUE; } LRESULT OnClose( UINT, WPARAM, LPARAM, BOOL& ) { DestroyWindow(); m_owner.Detach(); return 0; } CMyWindow m_owner; CWindow m_ed; }; CMyDialog dlg; int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { _Module.Init( NULL, hInstance ); CMyWindow win; win.Create( NULL, CWindow::rcDefault, _T("modeless dialog test"), WS_OVERLAPPEDWINDOW|WS_VISIBLE ); dlg.Create( win ); dlg.ShowWindow( SW_SHOW ); MSG msg; while( GetMessage( &msg, NULL, 0, 0 ) ){ if( !IsWindow(dlg) || !dlg.IsDialogMessage( &msg ) ){ DispatchMessage( &msg ); } } _Module.Term(); return 0; }
CMyWindow wnd; wnd.Create( NULL, CWindow::rcDefault, _T("Hello"), WS_OVERLAPPEDWINDOW|WS_VISIBLE );如果你不指定任何样式和扩展样式,ATL将使用默认的样式;这些默认的样式是作为窗口的特征定义的,默认特征是CControlWinTraits,定义如下:
typedef CWinTraitsCWinTraits是一个模板类,它需要2个参数:窗口样式、扩展窗口样式。CControlWinTraits;
template所以在默认情况下,从CWindowImpl派生的窗口都具有可视、子窗口、裁剪兄弟窗口、裁减子窗口的属性。class CWindowImpl : public ...
typedef CWinTraits然后,从CWindowImpl派生一个窗口类,指定自己的窗口特征:MyTraits;
class CMyWindow: public CWindowImpl或者象下面这样更加直接:{...};
class CMyWindow: public CWindowImpl< CMyWindow, CWindow, CWinTraits注意,我们必须提供全部的三个模板参数:派生类,基类(CWindow)和特征类。> {...};
CMyWindow wnd; wnd.Create( NULL, CWindow::rcDefault, _T("Hello") ); // style: WS_OVERLAPPEDWINDOW|WS_VISIBLE我们也可以重写窗口特征:
ovwnd.Create( NULL, CWindow::rcDefault, _T("Hello"), WS_OVERLAPPEDWINDOW ); // not visible窗口特征也可以包含扩展样式:
class CClientWindow: public CWindowImplDECLARE_WND_CLASS> {...};
DECLARE_WND_CLASS("my window class");这等价于:
DECLARE_WND_CLASS_EX( "my window class", CS_HREDRAW|CS_VREDRAW|CS_DBLCLKS, // default style COLOR_WINDOW // default color );DECLARE_WND_CLASS_EX
class CMyWindow: public CWindowImpl所谓的窗口类名是指注册的窗口类的名字,如果我们不指定窗口类名,ATL将自动生成一个,但是当我们使用Spy 之类的工具的时候,你将会发现我们自己取的类名比"ATL:00424bd0"之类的名字要有用得多。{ public: DECLARE_WND_CLASS_EX( "my window class", // class name CS_HREDRAW|CS_VREDRAW, // class style COLOR_WINDOW // background color ); BEGIN_MSG_MAP(CMyWindow) ...
#define DECLARE_WND_CLASS(WndClassName) \ static CWndClassInfo& GetWndClassInfo() \ { \ static CWndClassInfo wc = \ { \ { sizeof(WNDCLASSEX), CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS, \ StartWindowProc, \ 0, 0, NULL, NULL, NULL, (HBRUSH)(COLOR_WINDOW 1), NULL, \ WndClassName, NULL }, \ NULL, NULL, IDC_ARROW, TRUE, 0, _T("") \ }; \ return wc; \ }CWndClassInfo结构提供了更灵活的自定义的可能,它是这样定义的:
struct CWndClassInfo { struct WNDCLASSEX { UINT cbSize; UINT style; WNDPROC lpfnWndProc; int cbClsExtra; int cbWndExtra; HINSTANCE hInstance; HICON hIcon; HCURSOR hCursor; HBRUSH hbrBackground; LPCSTR lpszMenuName; LPCSTR lpszClassName; HICON hIconSm; } m_wc; LPCSTR m_lpszOrigName; WNDPROC pWndProc; LPCSTR m_lpszCursorID; BOOL m_bSystemCursor; ATOM m_atom; CHAR m_szAutoName[13]; ATOM Register(WNDPROC* p); };例如,要指定一个窗口的指针,我们可以将m_lpszCursorID设置为指针的名字,如果它是一个系统指针,将m_bSystemCursor设置为TRUE,否则设置为FALSE。注意DECLARE_WND_CLASS宏是怎样将这两个成员变量分别设置为IDC_ARROW 和 TRUE的。既然DECLARE_WND_宏不能让我们改写这些默认的值,我们可以这样做:
class CMyWindow: public CWindowImpl结论:{ public: static CWndClassInfo& GetWndClassInfo() { // a manual DECLARE_WND_CLASS macro expansion // modified to specify an application-defined cursor: static CWndClassInfo wc = { { sizeof(WNDCLASSEX), CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS, StartWindowProc, 0, 0, NULL, NULL, NULL, (HBRUSH)(COLOR_WINDOW 1), NULL, "MyWindow", NULL }, NULL, NULL, MAKEINTRESOURCE(IDC_CURSOR1), FALSE, 0, _T("") }; return wc; } ...