一、实现对话框
ATL中有三个模板类可用于创建一个对话框:
(1)CSimpleDialog:创建模式对话框, 可以host Windows Controls
template < WORD t_wDlgTemplateID, BOOL t_bCenter = TRUE > class CSimpleDialog : public CDialogImplBase |
(2)CDialogImpl:创建模式或非模式对话框, 可以host Windows Controls
template < class T, class TBase = CWindow > class ATL_NO_VTABLE CDialogImpl : public CDialogImplBaseT< TBase > |
(3)CAxDialogImpl:创建模式或非模式对话框, 可以host Windows Controls和ActiveX Controls
template < class T, class TBase = CWindow > class ATL_NO_VTABLE CAxDialogImpl : public CDialogImplBaseT< TBase > |
用CSimpleDialog和CDialogImpl不能显示含有ActiveX控件的对话框,只有CAxDialogImpl可以。如果想处理对话框中ActiveX控件的事件,在OnInitDialog()中加入AtlAdviseSinkMap(this, TRUE)。在退出时加入 AtlAdviseSinkMap(this, FALSE)。通过Insert/New ATL Object/miscellaneous/Dialog生成的对话框缺省就是从CAxDialogImpl继承的。
这三个类的使用方法很类似。都是派生出一个新类,并确保有一个IDD的成员指明资源ID。如:
class CMyDialog : public CDialogImpl, ... { public: enum { IDD = IDD_MYDIALOG }; //必须要有IDD这个成员,一般都是enum型 BEGIN_MSG_MAP(CMyDialog) LRESULT OnInitDialog(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) |
CSimpleDialog的使用可以更简单一些,如下:
CSimpleDialog< IDD_MYDIALOG > dlg ; dlg.DoModal() ; |
另外CSimpleDialog对IDOK和IDCANCEL有内在的支持,即会自动调用EndDialog,在其定义中包含如下代码:
BEGIN_MSG_MAP(thisClass) MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog) COMMAND_RANGE_HANDLER(IDOK, IDNO, OnCloseCmd) END_MSG_MAP() ...... LRESULT OnCloseCmd(WORD /*wNotifyCode*/, WORD wID, HWND /*hWndCtl*/, BOOL& /*bHandled*/) |
但CDialogImpl和CAxDialogImpl没有内在的支持,所以必须自己加消息处理函数调用EndDialog,比如:
COMMAND_ID_HANDLER(IDOK, OnOK) COMMAND_ID_HANDLER(IDCANCEL, OnCancel) LRESULT OnOK(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled) LRESULT OnCancel(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled) |
二、subclass和superclass
subclass的作用就是截获消息,指向自己的容器过程函数。有两种subclass。
(1)instance subclass:子类化一个已有的容器对象实例。每个窗口对象实例都由系统分配了一段内存空间(HWND指的就是这个内存块),保存有窗口的各种信息,包括WndProc的地址。所以可以改写这个地址为自己的WndProc地址,从而可以截获对消息的处理。(当然也要保留原来的WndProc地址,以pass message)
(2)global subclass:子类化一个window class。在WNDCLASS中保留有WndProc的地址,所以可以改变它为自己的WndProc的地址,这样所有基于这个新的WNDCLASS创建的对象实例都将指向新的WndProc的地址。如图所示:
instance subclass
|
global subclass
|
superclass作用与subclass一样,有两点区别:(1)没有instance superclass,只能superclass窗口类。(2)subclass不能截获WM_CREATE、WM_NCCREATE等创建消息,而superclass可以截获,但也一定要pass给原来的WndProc以完成初始化工作。
三、使用CWindowImpl实现一个窗口
CWindowImpl是个模板类,其定义如下:
template < class T, class TBase = CWindow, class TWinTraits = CControlWinTraits > class ATL_NO_VTABLE CWindowImpl : public CWindowImplBaseT< TBase, TWinTraits > template < class TBase = CWindow, class TWinTraits = CControlWinTraits > template < class TBase = CWindow > |
可见,对于CWindowImpl< CMyWindow >来说,最终的基类仍是CWindow(缺省情况下)。模板参数TWinTraits主要用于指明所要创建窗口的style。ATL已经内建了一些类型:CControlWinTraits、CFrameWinTraits、CMDIChildWinTraits等,缺省的是CControlWinTraits,包含的style有 WM_CHILD | WM_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS。当然也可以自己定义窗口的style和extended styles。
template < DWORD t_dwStyle = 0, DWORD t_dwExStyle = 0 > class CWinTraits { public: static DWORD GetWndStyle(DWORD dwStyle) { return dwStyle == 0 ? t_dwStyle : dwStyle; } static DWORD GetWndExStyle(DWORD dwExStyle) { return dwExStyle == 0 ? t_dwExStyle : dwExStyle; } }; typedef CWinTraits< WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS, 0 > |
(1)使用superclass
class CMyWindow : public CWindowImpl< CMyWindow >, ... { public: DECLARE_WND_SUPERCLASS(NULL, "EDIT") // 消息映射宏 // 消息处理函数 } ; CMyWindow wnd ; (2)使用instance subclass class CMyWindow : public CWindowImpl< CMyWindow >, ... |
(3)创建新的窗口
class CMyWindow : public CWindowImpl< CMyWindow >, ... { public: DECLARE_WND_CLASS("MyWindow") // 仅仅定义一个类名,也可以为NULL,系统会自动生成一个类名 // 消息映射宏 // 消息处理函数 } ; CMyWindow wnd ; |
四、消息映射宏
ATL里定义了一组消息映射宏,一个典型的例子如下:
BEGIN_MSG_MAP(CMyClass) MESSAGE_HANDLER(WM_PAINT, OnPaint) CHAIN_MSG_MAP(CMyBaseClass) // CMyBaseClass表示基类 ALT_MSG_MAP(1) // ATL_MSG_MAP主要用于"contained window" CHAIN_MSG_MAP(CMyBaseClass) ALT_MSG_MAP(2) MESSAGE_HANDLER(WM_CHAR, OnChar) CHAIN_MSG_MAP_ALT(CMyBaseClass, 1) END_MSG_MAP() |
宏展开的结果大致如下:
BOOL CMyClass::ProcessWindowMessage(HWND, UINT, WPARAM, LPARAM, LRESULT& lResult, DWORD dwMsgMapID) { BOOL bHandled = TRUE ;// 该变量用于判断消息是否已处理过 ... switch(dwMsgMapID) // 缺省为0 { case 0: if (uMsg == WM_PAINT) { bHandled = TRUE ; lResult = OnPaint(uMsg, wParam, lParam, bHandled) ; // bHandled是引用型参数 if (bHandled) return TRUE ; } if (CMyBaseClass::ProcessWindowMessage(hWnd, uMsg, wParam, lParam, lResult)) return TRUE ; break ; case 1: // ALT_MSG_MAP(1),分支作用 if CMyBaseClass::ProcessWindowMessage(hWnd, uMsg, wParam, lParam, lResult)) return TRUE ; break ; case 2: // ALT_MSG_MAP(1) if (uMsg == WM_CHAR) { bHandled = TRUE ; lResult = OnChar(uMsg, wParam, lParam, bHandled) ; if (bHandled) return TRUE ; } if CMyBaseClass::ProcessWindowMessage(hWnd, uMsg, wParam, lParam, lResult, 1)) // 选择分支1 return TRUE ; break ; default: ... } return FALSE ; } |
ProcessWindowMessage()是虚函数,论其根源在CMessageMap中定义,CMessageMap是CWindowImpl的基类之一(另一个是CWindow)。一般在WindowProc中调用ProcessWindowMessage,若调用失败,即返回FALSE,则调用DefWindowProc,而DefWindowProc则调用成员变量m_pfnSuperWindowProc所指向的窗口过程,这个变量用于subclass和superclass时保留原始的窗口过程指针。
CHAIN_MSG_MAP给了一个机会,可以执行theChainClass(一般就是父类)的ProcessWindowMessage函数,它一般用于ActiveX Control的实现中,如:CHAIN_MSG_MAP(CComControl< CMyCtl >)。这时该宏是必要的,因为父类可能处理了某些消息,经如在CComControl的类定义中就有下面几条语句:
MESSAGE_HANDLER(WM_PAINT, CComControlBase::OnPaint) MESSAGE_HANDLER(WM_SETFOCUS, CComControlBase::OnSetFocus) MESSAGE_HANDLER(WM_KILLFOCUS, CComControlBase::OnKillFocus) MESSAGE_HANDLER(WM_MOUSEACTIVATE, CComControlBase::OnMouseActivate) |
可见CComControl包含了对WM_PAINT、WM_SETFORCE、WM_KILLFOCUS、WM_MOUSEACTIVE四个消息的处理。只有在父类也处理不了这个消息的情况下才应该去调用DefWindowProc。
五、使用Contained Window
contained window被包含在其他对象中,典型用法如下:
class CMyContainer : public CComControl< CMyContainer >, ... // a full control { public: CContainedWindow m_wndEdit, m_wndList ; CMyContainer() : m_wndEdit("Edit", this, 1) // 1表示分支 , m_wndList("List", this, 2) // 2表示分支 { ... } BEGIN_MSG_MAP(CMyContainer) LRESULT OnCreate(...) |
比较CWindowImpl和CContainedWindow的消息处理过程:
(1)CWindowImpl:
(2)CContainedWindow:
可见contained window先是由包含它的container处理它的消息,如果处理不了才转到它自己的DefWindowProc。
六、实现hosting ActiveX control
ATL也提供了一些类以实现可以hosting ActiveX control的窗口。前面提过的CAxDialogImpl就是这样的类,另外CAxWindow可适用于更通用的窗口。它的使用方法也非常简单,下面是一个简单的例子,实现一个窗口,包含一个日历控件。
(1)声明如下形式的类定义:
class CAtlAxWindow : public CWindowImpl< CAtlAxWindow, CWindow, CFrameWinTraits > , public IDispEventImpl< 1, CAtlAxWindow, &DIID_DCalendarEvents, &LIBID_MSACAL, 7, 0 > { public: // 窗口消息 BEGIN_MSG_MAP(CAtlAxWindow) ... END_MSG_MAP() // 事件处理 private: }; |
从CWindowImpl继承,是为了创建一个包含ActiveX控件的父窗口,这里用了CFrameWinTraits,表明创建Frame样式的窗口。从IDispEventImpl继承,是为了能接受被包含控件的事件。
(2)创建hosting窗口有两种方法,分别如下:
// 方法一 RECT rect = { 0, 0, 400, 300 }; m_axwnd.Create(m_hWnd, rect, _T("MSCAL.Calendar.7"), WS_OVERLAPPEDWINDOW | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN, 0); m_axwnd.CenterWindow() ; m_axwnd.SetWindowText(_T("ActiveX Host Window")) ; SubclassWindow(m_axwnd.m_hWnd) ; // 方法二,也可以不使用m_axwnd,在Create中设置WindowName为"AtlAxWin"直接创建 |
这几句话非常简单,具体函数的含义可查查MSDN。
(3)创建完hosting窗口后,要建立事件连接,如下代码:
// 建立事件连接 CComPtr< IUnknown > spIUnknown ; m_axwnd.QueryControl(IID_IUnknown, (void**)&spIUnknown) ; DispEventAdvise(spIUnknown, &DIID_DCalendarEvents) ; |
效果如图所示: