在ATL中實現視窗(精)

來源:互聯網
上載者:User
                                在ATL中實現視窗

 

    ATL提供了一些工具類以實現視窗,這樣在ATL組件裡就可以不依賴MFC而很方便的建立視窗。

    一、實現對話方塊

    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)
MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog)
END_MSG_MAP()

LRESULT OnInitDialog(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
return 1;
}
};
CMyDialog dlg ;
dlg.DoModal() ;

    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*/)
{
::EndDialog(m_hWnd, wID);
return 0;
}

    但CDialogImpl和CAxDialogImpl沒有內在的支援,所以必須自己加訊息處理函數調用EndDialog,比如:

COMMAND_ID_HANDLER(IDOK, OnOK)
COMMAND_ID_HANDLER(IDCANCEL, OnCancel)

LRESULT OnOK(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
{
EndDialog(wID);
return 0;
}

LRESULT OnCancel(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
{
EndDialog(wID);
return 0;
}

    二、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 >
class ATL_NO_VTABLE CWindowImplBaseT : public CWindowImplRoot< TBase >

template < class TBase = CWindow >
class ATL_NO_VTABLE CWindowImplRoot : public TBase, public CMessageMap

    可見,對於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 >
CControlWinTraits;
typedef CWinTraits< WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN | WS_CLIPSIBLINGS, WS_EX_APPWINDOW | WS_EX_WINDOWEDGE >
CFrameWinTraits;
typedef CWinTraits< WS_OVERLAPPEDWINDOW | WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS, WS_EX_MDICHILD >
CMDIChildWinTraits;

    (1)使用superclass

class CMyWindow : public CWindowImpl< CMyWindow >, ...
{
public:
DECLARE_WND_SUPERCLASS(NULL, "EDIT")
// 訊息映射宏
// 訊息處理函數
} ;

CMyWindow wnd ;
wnd.Create(...) ;

    (2)使用instance subclass

class CMyWindow : public CWindowImpl< CMyWindow >, ...
{
public:
// 訊息映射宏
// 訊息處理函數
} ;
CMyWindow wnd ;
wnd.SubclassWindow(hSomeWnd) ;// 已有視窗的控制代碼

    (3)建立新的視窗

class CMyWindow : public CWindowImpl< CMyWindow >, ...
{
public:
DECLARE_WND_CLASS("MyWindow")// 僅僅定義一個類名,也可以為NULL,系統會自動產生一個類名
// 訊息映射宏
// 訊息處理函數
} ;

CMyWindow wnd ;
wnd.Create(...) ;

    四、訊息映射宏

    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),分支作用
ifCMyBaseClass::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 ;
}
ifCMyBaseClass::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)
// 處理自己的訊息
ALT_MSG_MAP(1)// case 1 :
// 處理edit容器的訊息
ALT_MSG_MAP(2)// case 2 :
// 處理list容器的訊息
END_MSG_MAP()

LRESULT OnCreate(...)
{
m_wndEdit.Create(...) ;
m_wndList.Create(...) ;
}
......
} ;

    比較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()

// 事件處理
BEGIN_SINK_MAP(CAtlAxWindow)
SINK_ENTRY_EX(1, DIID_DCalendarEvents, 0x1, OnAfterUpdateCalendar)
...
END_SINK_MAP()
STDMETHOD(OnAfterUpdateCalendar)() ;

private:
CAxWindow m_axwnd ;// host activex control

};

    從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"直接建立
RECT rect = { 0, 0, 400, 300 };
Create(NULL, rect, NULL, WS_OVERLAPPEDWINDOW | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN);
CenterWindow() ;
SetWindowText(_T("ActiveX Host Window")) ;
m_axwnd.Attach(m_hWnd) ;
m_axwnd.CreateControl(OLESTR("MSCAL.Calendar.7")) ;

    這幾句話非常簡單,具體函數的含義可查查MSDN。

    (3)建立完hosting視窗後,要建立事件串連,如下代碼:

// 建立事件串連
CComPtr< IUnknown > spIUnknown ;
m_axwnd.QueryControl(IID_IUnknown, (void**)&spIUnknown) ;
DispEventAdvise(spIUnknown, &DIID_DCalendarEvents) ;

    效果:

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.