天天看點

ATL GUI (五)

一點一點的搞界面好無聊,還是直接搞ActiveX吧,沒有比這個爽的

再說本來就是用來搞這個的。

這一節直接搞個例子玩,

先使用無模式對話框,要選上支援ActiveX控件包容

添加一個浏覽器的ActiveX的控件。

在對話框中使用ActiveX控件需要兩個類協同工作:CAxDialogImpl和CAxWindow。

它們處理所有控件容器必須實作的接口方法,提供通用的功能函數,例如查詢控件的某個特殊的COM接口。

CAxDialogImpl

第一個類是CAxDialogImpl,你的對話框要能夠包容控件就必須從CAxDialogImpl類派生而不是從CDialogImpl類派生。

CAxDialogImpl類重載了Create()和DoModal()函數,這兩個函數分别被全局函數AtlAxCreateDialog() 和AtlAxDialogBox()調用。

既然IEHoster對話框是由Create()建立的,我們看看AtlAxCreateDialog()到底做了什麼工作。

AtlAxCreateDialog()使用輔助類_DialogSplitHelper 裝載對話框資源,這個輔助類周遊是以對話框的控件,

查找由資源編輯器建立的特殊的入口,這些特殊的入口表示這是一個ActiveX控件。例如,下面是 IEHoster.rc檔案中浏覽器控件的入口:

CONTROL "",IDC_IE,"{8856F961-340A-11D0-A96B-00C04FD705A2}",

        WS_TABSTOP,7,7,116,85

第一個參數是視窗文字(空字元串),第二個是控件的ID,第三個是視窗的類名。

_DialogSplitHelper:: SplitDialogTemplate()函數找到以'{'開始的視窗類名時就知道這是一個ActiveX控件的入口。

它在記憶體中建立了一個臨時對話框模闆,在這個新模闆中這些特殊的控件入口被建立的AtlAxWin視窗代替,

新的入口是在記憶體中的等價體:

CONTROL "{8856F961-340A-11D0-A96B-00C04FD705A2}",IDC_IE,"AtlAxWin",

        WS_TABSTOP,7,7,116,85

結果就是建立了一個相同ID的AtlAxWin視窗,視窗的标題是ActiveX控件的GUID。

是以你調用GetDlgItem(IDC_IE)傳回的值是AtlAxWin視窗的句柄而不是ActiveX控件本身。

SplitDialogTemplate()函數完成工作後,AtlAxCreateDialog()接着調用CreateDialogIndirectParam()函數使用修改後的模闆建立對話框。

AtlAxWin and CAxWindow

AtlAxWin實際上是ActiveX控件的宿主視窗,AtlAxWin還會用到一個特殊的視窗接口類: CAxWindow,當AtlAxWin從模闆建立一個對話框後,

AtlAxWin的視窗處理過程,AtlAxWindowProc(),就會處理 WM_CREATE消息并建立相應的ActiveX控件。ActiveX控件還可以在運作其間動态建立,

不需要對話框模闆,

WM_CREATE的消息處理函數調用全局函數 AtlAxCreateControl(),将AtlAxWin視窗的視窗标題作為參數傳遞給該函數,

大家應該記得那實際就是浏覽器控件的GUID。 AtlAxCreateControl()有會調用一堆其他函數,

不過最終會用到CreateNormalizedObject()函數,這個函數将視窗标題轉換成GUID,并最終調用CoCreateInstance()建立ActiveX控件。

(總算是建立了一個com,等了好久,原來在這裡)

由于ActiveX控件是AtlAxWin的子視窗,是以對話框不能直接通路控件,當然CAxWindow提供了這些方法通控件通信,

最常用的一個是QueryControl(),這個方法調用控件的QueryInterface()方法。例如,你可以使用QueryControl()從浏覽器控件得到IWebBrowser2接口,

然後使用這個接口将浏覽器引導到指定的URL。

調用控件的方法

CAxWindow wndIE = GetDlgItem(IDC_IE);

CComPtr<IWebBrowser2> pWB2;

HRESULT hr;

hr = wndIE.QueryControl ( &pWB2 );

QueryControl()調用浏覽器控件的QueryInterface()方法,如果成功就會傳回IWebBrowser2接口,我們可以調用Navigate():

if ( pWB2 )

        {

        CComVariant v;  // empty variant

        pWB2->Navigate ( CComBSTR("http://www.codeproject.com/"),

                         &v, &v, &v, &v );

        }

響應控件觸發的事件

從浏覽器控件得到接口非常簡單,通過它可以單向的與控件通信。通常控件也會以事件的形式與外界通信,ATL有專用的類包裝連接配接點和事件相應,是以我們可以從控件接收到這些事件。為使用對事件的支援需要做四件事:

   1. 将CMainDlg變成COM對象

   2. 添加IDispEventSimpleImpl到CMainDlg的繼承清單

   3. 填寫事件映射鍊,它訓示哪些事件需要處理

   4. 編寫事件響應函數

(其實就是寫個sink)

将CMainDlg轉變成COM對象的原因是事件相應是基于IDispatch的,為了讓CMainDlg暴露這個接口,它必須是個COM對象。

IDispEventSimpleImpl提供了IDispatch接口的實作和建立連接配接點所需的處理函數,當事件發生時 IDispEventSimpleImpl還調用我們想要接收的事件的處理函數。

做點小小的修改就可以把一個普通的類變成一個com

加頭,沒有這個就沒有哪些GUID定義(好像#import也行哦)

#include <exdisp.h>    // browser control definitions

#include <exdispid.h>  // browser event dispatch ID

加入繼承

 public CComObjectRootEx<CComSingleThreadModel>,

 public CComCoClass<CMainDlg>,

 public IDispEventSimpleImpl<37, CMainDlg, &DIID_DWebBrowserEvents2>

加個宏

BEGIN_COM_MAP(CMainDlg)

    COM_INTERFACE_ENTRY2(IDispatch, IDispEventSimpleImpl)

END_COM_MAP()

CComObjectRootEx類CComCoClass共同使CMainDlg成為一個COM對象, IDispEventSimpleImpl的模闆參數是事件的ID,

我們的類名和連接配接點接口的IID。事件ID可以是任意正數,連接配接點對象的IID是 DIID_DWebBrowserEvents2,

可以在浏覽器控件的相關文檔中找到這些參數,也可以檢視exdisp.h。

寫個這個

#define _ATL_MAX_VARTYPES 8

struct _ATL_FUNC_INFO

{

    CALLCONV cc;

    VARTYPE  vtReturn;

    SHORT    nParams;

    VARTYPE  pVarTypes[_ATL_MAX_VARTYPES];

};

參數:

cc

    我們的事件響應函數的調用方式約定,這個參數必須是CC_STDCALL,表示是__stdcall方式

vtReturn

    事件響應函數的傳回值類型

nParams

    事件帶的參數個數

pVarTypes

    相應的參數類型,按從左到右的順序

了解這些之後,我們就可以填寫DownloadBegin事件處理的_ATL_FUNC_INFO結構:

_ATL_FUNC_INFO DownloadInfo = { CC_STDCALL, VT_EMPTY, 0 };

現在,回到事件響應鍊,我們為每一個我們想要處理的事件添加一個SINK_ENTRY_INFO宏,下面是處理DownloadBegin事件的宏:

class CMainDlg : public ...

{

...

  BEGIN_SINK_MAP(CMainDlg)

    SINK_ENTRY_INFO(37, DIID_DWebBrowserEvents2, DISPID_DOWNLOADBEGIN,

                    OnDownloadBegin, &DownloadInfo)

  END_SINK_MAP()

};

這個宏的參數是事件的ID(37,與我們在IDispEventSimpleImpl的繼承清單中使用的ID一樣),事件接口的IID,

事件的dispatch ID(可以在MSDN或exdispid.h頭檔案中查到),事件處理函數的名字和指向描述這個事件處理的_ATL_FUNC_INFO結構的指針。

将CMainDlg轉換成對象需要注意幾件事情,首先必須修改全局函數Run(),現在CMainDlg是個COM對象,我們必須使用CComObject建立CMainDlg:

int Run(LPTSTR = NULL, int nCmdShow = SW_SHOWDEFAULT)

{

    CMessageLoop theLoop;

    _Module.AddMessageLoop(&theLoop);

CComObject<CMainDlg> dlgMain;

    dlgMain.AddRef();

    if ( dlgMain.Create(NULL) == NULL )

        {

        ATLTRACE(_T("Main dialog creation failed!/n"));

        return 0;

        }

    dlgMain.ShowWindow(nCmdShow);

    int nRet = theLoop.Run();

    _Module.RemoveMessageLoop();

    return nRet;

}

運作時建立ActiveX控件

出了使用資源編輯器,還可以在運作其間動态建立ActiveX控件。About對話框示範了這種技術。對話框編輯器預先放置了一個group box用于浏覽器控件的定位:

在OnInitDialog()函數中我們使用 CAxWindow建立了一個新AtlAxWin,它定位于我們預先放置好的group box的位置上(這個group box随後被銷毀):

LRESULT CAboutDlg::OnInitDialog(...)

{

CWindow wndPlaceholder = GetDlgItem ( IDC_IE_PLACEHOLDER );

CRect rc;

CAxWindow wndIE;

    // Get the rect of the placeholder group box, then destroy

    // that window because we don't need it anymore.

    wndPlaceholder.GetWindowRect ( rc );

    ScreenToClient ( rc );

    wndPlaceholder.DestroyWindow();

    // Create the AX host window.

    wndIE.Create ( *this, rc, _T(""),

                   WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN );

接下來我們用CAxWindow方法建立一個ActiveX控件,有兩個方法可以選擇:CreateControl()和 CreateControlEx()。

CreateControlEx()用一個額外的參數傳回接口指針,這樣就不需要再調用QueryControl ()函數。

我們感興趣的兩個參數是第一個和第四個參數,第一個參數是字元串形式的浏覽器控件的GUID,第四個參數是一個IUnknown*類型的指針,

這個指針指向ActiveX控件的IUnknown接口。建立控件後就可以查詢IWebBrowser2接口,然後就可以像前面一樣控制它導航到某個 URL。

CComPtr<IUnknown> punkCtrl;

CComQIPtr<IWebBrowser2> pWB2;

CComVariant v;

    // Create the browser control using its GUID.

    wndIE.CreateControlEx ( L"{8856F961-340A-11D0-A96B-00C04FD705A2}",

                            NULL, NULL, &punkCtrl );

    // Get an IWebBrowser2 interface on the control and navigate to a page.

    pWB2 = punkCtrl;

    pWB2->Navigate ( CComBSTR("about:mozilla"), &v, &v, &v, &v );

}

對于有ProgID的ActiveX控件可以傳遞ProgID給CreateControlEx(),代替GUID。例如,我們可以這樣建立浏覽器控件:

    // 使用控件的ProgID: 建立Shell.Explorer:

    wndIE.CreateControlEx ( L"Shell.Explorer", NULL,

                            NULL, &punkCtrl );

CreateControl()和CreateControlEx()還有一些重載函數用于一些使用浏覽器的特殊情況,如果你的應用程式使用 WEb頁面作為HTML資源,

你可以将資源ID作為第一個參數,ATL會建立浏覽器控件并導航到這個資源。IEHoster包含一個ID為 IDR_ABOUTPAGE的WEB頁面資源,

我們在About對話框中使用這些代碼顯示這個頁面:

    wndIE.CreateControl ( IDR_ABOUTPAGE );

鍵盤事件處理

最後一個但是非常重要的細節是鍵盤消息。ActiveX控件的鍵盤處理非常複雜,因為控件和它的宿主程式必須協同工作以確定控件能夠看到它感興趣的消息。例如,浏覽器控件允許你使用TAB鍵在連結之間切換。MFC自己處理了所有工作,是以你永遠不會意識到讓鍵盤完美并正确的工作需要多麼大的工作量。

不幸的是向導沒有為基于對話框的程式生成鍵盤處理代碼,當然,如果你使用Form View作為視圖類的SDI程式,你會看到必要的代碼已經被添加到PreTranslateMessage()中。當程式從消息隊列中得到滑鼠或鍵盤消息時,就使用ATL的WM_FORWARDMSG消息将此消息傳遞給目前擁有焦點的控件。它們通常不作什麼事情,但是如果是ActiveX控件, WM_FORWARDMSG消息最終被送到包容這個控件的AtlAxWin,AtlAxWin識别WM_FORWARDMSG消息并采取必要的措施看看是否控件需要親自處理這個消息。

如果擁有焦點的視窗沒有識别WM_FORWARDMSG消息,PreTranslateMessage()就會接着調用IsDialogMessage()函數,使得像TAB這樣的标準對話框的導航鍵能正常工作。

就是這個樣子,WTL對話框使用ActiveX控件就是這樣的

文章轉至http://www.imyaker.com/wtl/,加入了自己的了解。

繼續閱讀