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

更精準獲取 Win32 視窗控制代碼:解決 CHtmlView 中 IE_Server 子視窗查找難題

     編輯:關於VC++

本文延續自 MSDN Magazine 經典專欄「C++ Q&A」中 Paul DiLascia 的三篇文章:VC6 中使用 CHtmlView 在對話方塊顯示 HTML、如何禁用 HTML 頁面的右鍵選單、Convert CHtmlView to CHtmlCtrl。Paul DiLascia 自 1995 年起深耕 Windows 應用開發,其見解至今仍具參考價值。若您已熟悉上述文章,可直接閱讀核心內容;否則建議先行回顧以建立背景知識。

背景:CHtmlView 與 Internet Explorer_Server 視窗

在早期文章中,作者展示了如何透過衍生 CHtmlCtrl 類別,使 CHtmlView 擺脫文檔/視圖架構限制,支援對話方塊應用。實際渲染 HTML 的並非瀏覽器視窗本身,而是一個名為 Internet Explorer_Server 的深層子孫視窗。早期範例使用 GetLastChild 遞迴查找最底層子視窗:

static HWND GetLastChild(HWND hwndParent) {
    HWND hwnd = hwndParent;
    while (TRUE) {
        HWND hwndChild = ::GetWindow(hwnd, GW_CHILD);
        if (hwndChild == NULL) return hwnd;
        hwnd = hwndChild;
    }
    return NULL;
}

此函數假設視窗層級單一且最終後裔必為 Internet Explorer_Server。然而,當 HTML 文件包含 ComboBox 等控制項時,此假設失效——IE 中的 Edit、Button 控制項並非一般預期的子視窗結構,導致查找失誤。

更健壯的解決方案:CFindWnd 類別

為解決此問題,本文設計了 CFindWnd 類別,採用更完善的演算法專門查找 IE 視窗:

CFindWnd ies(m_hWnd, "Internet Explorer_Server");
myHwndIE = ies.m_hWnd;

其建構函數呼叫 FindChildClassHwnd,內部結合 EnumChildWindows 與 FindWindowEx 遞迴搜尋所有後裔視窗,直到找到類別名稱匹配的視窗為止。FindWindowEx 為 Win32 API,專門用於搜尋子視窗;CFindWnd 回傳第一個匹配項,適用於預期唯一實例的視窗查找。檢查視窗類別名稱是搜尋特定視窗最可靠的做法。

讀者來信:更簡潔的右鍵選單禁用法

有讀者指出,無須子類化 IE 視窗,直接在 CHtmlCtrl 內部覆寫 PreTranslateMessage 即可攔截右鍵選單訊息:

BOOL CHtmlCtrl::PreTranslateMessage(MSG* pMsg) {
    if (pMsg->message == WM_CONTEXTMENU) return TRUE; // 吞噬訊息
    return CHtmlView::PreTranslateMessage(pMsg);
}

此法可行,得益於 MFC 在 CWinThread 主訊息迴圈中呼叫 CWnd::WalkPreTranslateTree,會沿著父視窗鏈路呼叫各層的 PreTranslateMessage,一旦攔截即停止傳遞。但經驗證,仍需同時攔截 WM_RBUTTONDOWN、WM_RBUTTONDBLCLK 並檢查目標視窗類別名為 Internet Explorer_Server,以免誤捕獲其他子視窗的右鍵選單。

完整實作:功能增強的 CHtmlCtrl

本文提供的 CHtmlCtrl 類別整合了命令對應機制、動態 HTML 產生、右鍵選單控制等功能。關鍵程式碼片段:

// 攔截 IE_Server 視窗的右鍵選單相關訊息
BOOL CHtmlCtrl::PreTranslateMessage(MSG* pMsg) {
    if (m_bHideMenu) {
        switch (pMsg->message) {
            case WM_CONTEXTMENU:
            case WM_RBUTTONUP:
            case WM_RBUTTONDOWN:
            case WM_RBUTTONDBLCLK:
                if (IsIEWindow(pMsg->hwnd)) {
                    if (pMsg->message == WM_RBUTTONUP)
                        GetParent()->SendMessage(WM_CONTEXTMENU, pMsg->wParam, pMsg->lParam);
                    return TRUE;
                }
        }
    }
    return CHtmlView::PreTranslateMessage(pMsg);
}

// 判斷是否為 IE_Server 視窗
inline BOOL IsIEWindow(HWND hwnd) {
    static LPCSTR IEWNDCLASSNAME = "Internet Explorer_Server";
    char classname[32];
    GetClassName(hwnd, classname, sizeof(classname));
    return strcmp(classname, IEWNDCLASSNAME) == 0;
}

此實作採用發送 WM_CONTEXTMENU 給父視窗而非直接過濾,保留了自訂右鍵選單的彈性。注意 WM_CONTEXTMENU 於滑鼠右鍵釋放時觸發,非按下時。

動態產生並顯示 HTML 文件

若需動態顯示生成的 HTML(而非從資源載入),可使用 CHtmlCtrl::SetHTML 方法,內部透過 COM 介面 IHTMLDocument2、BSTR、SAFEARRAY 操作文件內容。ATL 的 CComBSTR、CComSafeArray 大幅簡化了字串處理:

HRESULT CHtmlCtrl::SetHTML(LPCTSTR strHTML) {
    HRESULT hr;
    SPIHTMLDocument2 doc = GetHtmlDocument();
    CComSafeArray<VARIANT> sar;
    sar.Create(1, 0);
    sar[0] = CComBSTR(strHTML);
    LPDISPATCH lpdRet;
    HRCHECK(doc->open(CComBSTR("text/html"), CComVariant(CComBSTR("_self")), CComVariant(CComBSTR("")), CComVariant((bool)1), &lpdRet));
    HRCHECK(doc->write(sar));
    HRCHECK(doc->close());
    lpdRet->Release();
    return S_OK;
}

使用技巧:首次建立 CHtmlCtrl 時無文件物件,需先 Navigate(_T("about:blank")) 建立空文件;簡單 HTML 可直接用 about:<HTML>...</HTML> 格式。

範例程式:動態列出所有可見頂層視窗

附帶範例程式動態構建包含圖片、表格、超連結的 HTML 文件,透過 EnumWindows 列舉頂層視窗並顯示其控制代碼、類別名稱、標題文字(圖 1)。

範例程式執行畫面

圖 1:範例程式執行畫面

核心邏輯在 CMainFrame::FormatWindowListHTML 中,構建 HTML 表格並嵌入 app:about、app:exit 等偽協議連結,配合命令對應機制實現 HTML 與應用程式命令的雙向通訊。

命令對應機制:簡化 app: 協議處理

透過 HTMLCMDMAP 結構與 SetCmdMap 方法,將 app:command 映射為標準 WM_COMMAND ID:

HTMLCMDMAP MyHtmlCmds[] = {
    { _T("about"), ID_APP_ABOUT },
    { _T("exit"), ID_APP_EXIT },
    { NULL, 0 }
};
m_wndHtmlCtrl.SetCmdMap(MyHtmlCmds);

當使用者點擊 <A HREF="app:about"> 時,OnAppCmd 會查找對應表並發送 WM_COMMAND 給父視窗,利用 MFC 命令路由機制由任意視窗處理。關鍵點:必須使用 PostMessage 而非 SendMessage,避免導航過程中關閉程式造成死結。

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