WTL 本身未新增 ActiveX 容器類別,直接使用 ATL 完善的 CAxDialogImpl/CAxWindow 機制。本章以 IE 瀏覽器控件 (WebBrowser) 為例,示範控件建立、方法呼叫、事件接收、鍵盤訊息處理完整流程。
CAxDialogImpl)。#include <atlcom.h> // CComPtr 等智慧指標
#include <atlhost.h> // ActiveX 容器視窗類別
#include <atlctl.h> // 控件相關
class CMainDlg : public CAxDialogImpl<CMainDlg>,
public CUpdateUI<CMainDlg>,
public CMessageFilter, public CIdleHandler { ... };
_Module.Init(NULL, hInstance);
AtlAxWinInit(); // 註冊 AtlAxWin 視窗類別 (ActiveX 宿主視窗)
int nRet = Run(...);
IDC_IE資源腳本實際條目:
CONTROL "",IDC_IE,"{8856F961-340A-11D0-A96B-00C04FD705A2}",WS_TABSTOP,7,7,116,85
AtlAxCreateDialog 使用 _DialogSplitHelper 掃描對話框範本。{ 開頭 (GUID) 的控件項目,替換為 AtlAxWin 類別,視窗標題設為 GUID。AtlAxWin 視窗程序處理 WM_CREATE,呼叫 AtlAxCreateControl → CoCreateInstance 建立實際 ActiveX 控件 (為 AtlAxWin 子視窗)。GetDlgItem(IDC_IE) 取得的是 AtlAxWin 宿主視窗控制代碼,非控件本身。// 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);
}
#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 實作與連接點管理。// 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 表指標。
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*。
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; // 攔截廣告
}
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 替代方案。
BeforeNavigate2/NavigateComplete2 (導航控制)、DownloadBegin/DownloadComplete (等待提示)、CommandStateChange (前進/後退按鈕啟用)、StatusTextChange (狀態列文字)。IWebBrowser2 對應方法)。CBitmapButton、CListViewCtrl、DDX、CDialogResize。
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;
}

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) 的使用。