首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> VC >> 關於VC++

MFC 程式設計師的 WTL 入門指南 (七):對話框嵌入 ActiveX 控件與事件處理全攻略

     編輯:關於VC++

ActiveX 控件容器:ATL 的專長,WTL 直接繼承

WTL 本身未新增 ActiveX 容器類別,直接使用 ATL 完善的 CAxDialogImpl/CAxWindow 機制。本章以 IE 瀏覽器控件 (WebBrowser) 為例,示範控件建立、方法呼叫、事件接收、鍵盤訊息處理完整流程。

精靈建立支援 ActiveX 的專案

  1. 專案類型:Dialog Based,勾選 Enable ActiveX Control Hosting (對話框衍生自 CAxDialogImpl)。
  2. 次頁面「包容 ActiveX 控件」勾選與否不影響結果。

精靈生成關鍵代碼解析

stdafx.h 新增標頭檔

#include <atlcom.h>   // CComPtr 等智慧指標
#include <atlhost.h>  // ActiveX 容器視窗類別
#include <atlctl.h>   // 控件相關

主對話框類別宣告

class CMainDlg : public CAxDialogImpl<CMainDlg>,
                 public CUpdateUI<CMainDlg>,
                 public CMessageFilter, public CIdleHandler { ... };

WinMain 初始化

_Module.Init(NULL, hInstance);
AtlAxWinInit(); // 註冊 AtlAxWin 視窗類別 (ActiveX 宿主視窗)
int nRet = Run(...);

資源編輯器加入瀏覽器控件

  1. 對話框編輯器右鍵 → Insert ActiveX Control
  2. 選擇 Microsoft Web Browser,ID 設為 IDC_IE

資源腳本實際條目:

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

ATL 容器運作機制:_DialogSplitHelper 與 AtlAxWin

  1. AtlAxCreateDialog 使用 _DialogSplitHelper 掃描對話框範本。
  2. 發現類別名以 { 開頭 (GUID) 的控件項目,替換為 AtlAxWin 類別,視窗標題設為 GUID。
  3. 建立對話框後,AtlAxWin 視窗程序處理 WM_CREATE,呼叫 AtlAxCreateControl → CoCreateInstance 建立實際 ActiveX 控件 (為 AtlAxWin 子視窗)。
  4. 注意:GetDlgItem(IDC_IE) 取得的是 AtlAxWin 宿主視窗控制代碼,非控件本身。

呼叫控件方法:QueryControl 取得介面指標

// OnInitDialog 中
CAxWindow wndIE = GetDlgItem(IDC_IE);
CComPtr<IWebBrowser2> pWB2;
HRESULT hr = wndIE.QueryControl(&pWB2); // 內部呼叫 QueryInterface
if (pWB2) {
    CComVariant vEmpty;
    pWB2->Navigate(CComBSTR(L"https://www.codeproject.com/"), &vEmpty, &vEmpty, &vEmpty, &vEmpty);
}

接收控件事件:四步驟完成連接點處理

1. 修改主對話框為 COM 物件並加入事件介面

#include <exdisp.h>    // 控件定義
#include <exdispid.h> // 事件 DISPID

class CMainDlg : public CAxDialogImpl<CMainDlg>,
                 public CUpdateUI<CMainDlg>,
                 public CMessageFilter, public CIdleHandler,
                 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 使類別成為 COM 物件。
  • IDispEventSimpleImpl<事件ID, 類別, 連接點IID> 提供 IDispatch 實作與連接點管理。
  • 事件 ID (37) 為任意正整數,需與事件對應表一致。

2. 定義 _ATL_FUNC_INFO 描述事件簽章

// DownloadBegin: void DownloadBegin()
_ATL_FUNC_INFO DownloadInfo = { CC_STDCALL, VT_EMPTY, 0, {} };

// BeforeNavigate2: void BeforeNavigate2(IDispatch*, VARIANT*, VARIANT*, VARIANT*, VARIANT*, VARIANT*, VARIANT_BOOL*)
_ATL_FUNC_INFO BeforeNavigate2Info = {
    CC_STDCALL, VT_EMPTY, 7,
    { VT_DISPATCH,
      VT_VARIANT|VT_BYREF, VT_VARIANT|VT_BYREF, VT_VARIANT|VT_BYREF,
      VT_VARIANT|VT_BYREF, VT_VARIANT|VT_BYREF,
      VT_BOOL|VT_BYREF }
};

常用 VARTYPE:VT_EMPTY(void)、VT_BSTR(BSTR)、VT_I4(long)、VT_DISPATCH(IDispatch*)、VT_VARIANT(VARIANT)、VT_BOOL(VARIANT_BOOL)、VT_BYREF 表指標。

3. 建立事件對應表 (Sink Map)

BEGIN_SINK_MAP(CMainDlg)
    SINK_ENTRY_INFO(37, DIID_DWebBrowserEvents2, DISPID_DOWNLOADBEGIN, OnDownloadBegin, &DownloadInfo)
    SINK_ENTRY_INFO(37, DIID_DWebBrowserEvents2, DISPID_BEFORENAVIGATE2, OnBeforeNavigate2, &BeforeNavigate2Info)
END_SINK_MAP()

參數:事件ID、連接點IID、事件DISPID、處理函式名稱、_ATL_FUNC_INFO*。

4. 實作事件處理函式 (__stdcall 呼叫慣例)

void __stdcall CMainDlg::OnDownloadBegin()
{
    // 顯示 "Please wait..."
}

void __stdcall CMainDlg::OnBeforeNavigate2(
    IDispatch* pDisp, VARIANT* URL, VARIANT* Flags,
    VARIANT* TargetFrameName, VARIANT* PostData,
    VARIANT* Headers, VARIANT_BOOL* Cancel)
{
    CString sURL = URL->bstrVal;
    // 記錄 URL 或攔截導航
    if (sURL.Find(_T("doubleclick.net")) > 0) *Cancel = VARIANT_TRUE; // 攔截廣告
}

主對話框建立方式調整:CComObject

int Run(LPTSTR, int nCmdShow)
{
    CMessageLoop theLoop; _Module.AddMessageLoop(&theLoop);
    CComObject<CMainDlg> dlgMain; // 必須用 CComObject 建立 COM 物件
    dlgMain.AddRef();
    if (dlgMain.Create(NULL) == NULL) return 0;
    dlgMain.ShowWindow(nCmdShow);
    int nRet = theLoop.Run();
    _Module.RemoveMessageLoop(); return nRet;
}

CComObjectStack 適用短命物件,但 AddRef 會觸發斷言,不適合事件接收器。範例提供 CComObjectStack2 替代方案。

完整範例功能:IEHoster

  • 嵌入瀏覽器控件,處理 6 個事件:BeforeNavigate2/NavigateComplete2 (導航控制)、DownloadBegin/DownloadComplete (等待提示)、CommandStateChange (前進/後退按鈕啟用)、StatusTextChange (狀態列文字)。
  • 工具列按鈕:後退、前進、停止、重新整理 (呼叫 IWebBrowser2 對應方法)。
  • 事件記錄清單,可篩選顯示。
  • 整合前幾章技術:CBitmapButton、CListViewCtrl、DDX、CDialogResize。

IEHoster 執行畫面

執行期動態建立 ActiveX 控件

About 對話框示範:預先放置 Group Box 定位,OnInitDialog 中以 CAxWindow 動態建立。

LRESULT CAboutDlg::OnInitDialog(...)
{
    CWindow wndPlaceholder = GetDlgItem(IDC_IE_PLACEHOLDER);
    CRect rc; wndPlaceholder.GetWindowRect(rc); ScreenToClient(rc);
    wndPlaceholder.DestroyWindow();

    CAxWindow wndIE;
    wndIE.Create(*this, rc, _T(""), WS_CHILD|WS_VISIBLE|WS_CLIPCHILDREN);

    // 方法一:GUID
    CComPtr<IUnknown> punkCtrl;
    wndIE.CreateControlEx(L"{8856F961-340A-11D0-A96B-00C04FD705A2}", NULL, NULL, &punkCtrl);

    // 方法二:ProgID
    // wndIE.CreateControlEx(L"Shell.Explorer", NULL, NULL, &punkCtrl);

    // 方法三:HTML 資源
    // wndIE.CreateControl(IDR_ABOUTPAGE);

    CComQIPtr<IWebBrowser2> pWB2 = punkCtrl;
    CComVariant v; pWB2->Navigate(CComBSTR(L"about:mozilla"), &v, &v, &v, &v);
    return 0;
}

About 對話框瀏覽器

鍵盤訊息處理:關鍵細節

ActiveX 控件鍵盤處理極其複雜,需宿主程式協作。MFC 自動處理,WTL 需手動在 PreTranslateMessage 加入:

BOOL CMainDlg::PreTranslateMessage(MSG* pMsg)
{
    if (CWindow::IsDialogMessage(m_hWnd, pMsg)) return TRUE;
    // 轉發滑鼠/鍵盤訊息給擁有焦點的控件
    HWND hWndFocus = ::GetFocus();
    if (hWndFocus && ::IsChild(m_hWnd, hWndFocus))
        return (BOOL)::SendMessage(hWndFocus, WM_FORWARDMSG, 0, (LPARAM)pMsg);
    return FALSE;
}
  • WM_FORWARDMSG 由 AtlAxWin 識別並處理控件所需鍵盤訊息 (如 Tab 鍵在連結間導航)。
  • 若控件不處理,再嘗試 IsDialogMessage 處理標準對話框導航鍵。
  • 必須使用非模態對話框,模態對話框由系統訊息迴圈驅動,無法攔截 PreTranslateMessage。

下一章預告

回到框架視窗,介紹分隔視窗 (CSplitterWindow) 的使用。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved