1、概述
Internet Explorer提供了非常開發(fā)的接口,使開發(fā)人員不僅可以把其瀏覽器核心嵌入應(yīng)用程序,還可以通過各種接口以實現(xiàn)更深層的控制。本文就將介紹對瀏覽器進(jìn)行高級控制的話題之一——自定義上下文菜單。
2、最簡單的情況
自定義的IE及WebBrowser的上下文菜單,最簡單的方式就是在注冊表的HKEY_CURRENT_USER\Software\Microsoft\Internet Explorer\MenuExt下添加自定義的鍵值,步驟如下:
1) 添加一個新的鍵,其名稱即為將來顯示在上下文菜單中的菜單項名稱,如:
HKEY_CURRENT_USER\Software\Microsoft\Internet Explorer\MenuExt\&Google Search
2) 將新增的鍵的默認(rèn)值設(shè)置為一個包含腳本的網(wǎng)頁的URL(或文件路徑全名),該網(wǎng)頁中的腳本將在用戶點擊上下文菜單中的“Google Search”后被瀏覽器執(zhí)行。

3)在新增的鍵下還可以新建一個二進(jìn)制值Contexts,用以指定我們新增的菜單項針對特定的網(wǎng)頁對象是否出現(xiàn),其取值可以是如下值的組合(邏輯或)
Context Value
Default 0x1
Images 0x2
Controls 0x4
Tables 0x8
Text selection 0x10
Anchor 0x20
4) 還可以建立一個DWORD類型的Flags項并將其值設(shè)置為0x01,這將使得前述腳本在一個模態(tài)窗口中執(zhí)行,就好像是通過window.showModalDialog調(diào)用的,但不同的是在腳本中仍然可以訪問window對象。
5) 實例腳本如下:
通過修改注冊表自定義菜單的方法適用于Internet Explorer和WebBrowser,也具有良好的擴(kuò)展性。但我們?nèi)绻M麍?zhí)行的是不僅僅是腳本,二是自己的程序中代碼,這種方法就不適用了。
3、使用完全自定義的菜單
1) IDocHostUIhandler接口提供了一個ShowContextMenu方法,在需要顯示上下文菜單之前,MSHTML引擎就會調(diào)用實現(xiàn)了IDocHostUIHandler接口的
宿主程序的ShowContextMenu方法。
HRESULTIDocHostUIHandler::ShowContextMenu(
DWORD dwID,
POINT *ppt,
IUnknown *pcmdtReserved,
IDispatch *pdispReserved
);
dwID參數(shù)的意義與Contexts的組合類似;ppt為菜單的彈出點屏幕坐標(biāo);pcmdtReserved接口指向
IOleCommandTarget接口,可用于檢測網(wǎng)頁對象的狀態(tài)和執(zhí)行命令等操作。pdispReserved在IE5以上版本中指向的是網(wǎng)頁對象的
IDispatch接口,用以區(qū)分不同對象,比如我們可以這樣來獲得網(wǎng)頁對象的指針:
IHTMLElement *pElem;
HRESULT hr;
hr = pdispReserved->QueryInterface(IID_IHTMLElement, (void**)pElem);
if(SUCCEEDED (hr)) {
BSTR bstr;
pElem->get_tagName(bstr);
USES_CONVERSION;
ATLTRACE("TagName:%s\n", OLE2T(bstr));
SysFreeString(bstr);
pElem->Release();
}
如果我們在該方法中返回S_OK,則告訴MSHTML我們將使用自己的菜單(界面),如果是S_FALSE,則彈出默認(rèn)的菜單。
2) 實現(xiàn)
原理清楚之后,實現(xiàn)起來非常簡單,和彈出一般的菜單沒什么兩樣,舉例如下,顯示主框架的“文件菜單”:
HRESULT CMyHtmlView::OnShowContextMenu(DWORD dwID, LPPOINT ppt, IUnknown * pcmdtReserved, IDispatch *)
{
// 載入主菜單
HMENU hMenuParent = ::LoadMenu( ::AfxGetInstanceHandle(), MAKEINTRESOURCE(IDR_MAINFRAME) );
if (hMenuParent)
{
HMENU hMenu = ::GetSubMenu( hMenuParent, 0 ); // 取得“文件”子菜單
if (hMenu)
{
// 顯示菜單
TrackPopupMenuEx( hMenu,
TPM_LEFTALIGN | TPM_TOPALIGN,
ppt->x,
ppt->y,
::AfxGetMainWnd()->m_hWnd,
NULL );
}
::DestroyMenu( hMenuParent );
}
return S_OK;
}
4、自定義標(biāo)準(zhǔn)上下文菜單
1) 原理
更多的時候我們希望能在瀏覽器原來菜單的基礎(chǔ)上作一些修改,如刪掉“查看源文件”,添加自己的菜單項,等等,而不是完全不要原始的菜單,怎么辦呢?借助MSDN提供的例子,我們來看看:
HRESULT CBrowserHost::ShowContextMenu(DWORD dwID, POINT *ppt,IUnknown *pcmdTarget,IDispatch *pdispObject)
{
#define IDR_BROWSE_CONTEXT_MENU 24641
#define IDR_FORM_CONTEXT_MENU 24640
#define SHDVID_GETMIMECSETMENU 27
#define SHDVID_ADDMENUEXTENSIONS 53
HRESULT hr;
HINSTANCE hinstSHDOCLC;
HWND hwnd;
HMENU hMenu;
CComPtr spCT;
CComPtr spWnd;
MENUITEMINFO mii = {0};
CComVariant var, var1, var2;
hr = pcmdTarget->QueryInterface(IID_IOleCommandTarget, (void**)&spCT);
hr = pcmdTarget->QueryInterface(IID_IOleWindow, (void**)&spWnd);
hr = spWnd->GetWindow(&hwnd);//取得瀏覽器窗口句柄
hinstSHDOCLC = LoadLibrary(TEXT("SHDOCLC.DLL"));
if (hinstSHDOCLC == NULL)
{
// Error loading module -- fail as securely as possible
return;
}
hMenu = LoadMenu(hinstSHDOCLC, MAKEINTRESOURCE(IDR_BROWSE_CONTEXT_MENU));
hMenu = GetSubMenu(hMenu, dwID);
// Get the language submenu
hr = spCT->Exec(&CGID_ShellDocView, SHDVID_GETMIMECSETMENU, 0, NULL, &var);
mii.cbSize = sizeof(mii);
mii.fMask = MIIM_SUBMENU;
mii.hSubMenu = (HMENU) var.byref;
// Add language submenu to Encoding context item
SetMenuItemInfo(hMenu, IDM_LANGUAGE, FALSE, &mii);
// Insert Shortcut Menu Extensions from registry
V_VT(&var1) = VT_INT_PTR;
V_BYREF(&var1) = hMenu;
V_VT(&var2) = VT_I4;
V_I4(&var2) = dwID;
hr = spCT->Exec(&CGID_ShellDocView, SHDVID_ADDMENUEXTENSIONS, 0, &var1, &var2);
// Remove View Source
DeleteMenu(hMenu, IDM_VIEWSOURCE, MF_BYCOMMAND);//刪除“查看源文件”菜單項
// Show shortcut menu
int iSelection = ::TrackPopupMenu(hMenu,
TPM_LEFTALIGN | TPM_RIGHTBUTTON | TPM_RETURNCMD ,//返回用戶選擇的菜單命令I(lǐng)D
ppt->x,
ppt->y,
0,
hwnd,
(RECT*)NULL);
// Send selected shortcut menu item command to shell
LRESULT lr = ::SendMessage(hwnd, WM_COMMAND, iSelection, NULL);//發(fā)送到Internet Explorer_Server進(jìn)行內(nèi)部處理。
FreeLibrary(hinstSHDOCLC);
return S_OK;
}
從
上面的例子我們看出,基本的方法就是根據(jù)“shdoclc.dll”文件中的菜單資源建立菜單,再通過來自pcmdTarget的
IOlcCommandTarget接口獲得“編碼”菜單以及HKEY_CURRENT_USER\Software\Microsoft\
Internet
Explorer\MenuExt下的定義擴(kuò)展菜單,然后以TPM_RETURNCMD標(biāo)志調(diào)用TrackPopupMenu或
TrackPopupMenuEx彈出菜單,并將返回的菜單命令I(lǐng)D教給瀏覽器窗口進(jìn)行處理。這種方法可以調(diào)用許多通過瀏覽器無法直接調(diào)用的命令和對話框
(參閱:《Internet Explorer 編程簡述(五)調(diào)用IE隱藏的命令》)。
所以,我們只需要在彈出菜單之前做一些自定義操作即可達(dá)到修改默認(rèn)菜單的目的,如上面代碼中就用刪除了“查看源文件”菜單項。
2) 問題
如果我們不僅僅是刪除默認(rèn)的菜單項或是修改了默認(rèn)的菜單項,還添加了自己的菜單項,會出現(xiàn)什么情況呢?由于使用了類似于MFC中UpdateUI的機(jī)制,遇到不認(rèn)識的CommandID,瀏覽器就會將其狀態(tài)設(shè)置為Disabled,所以我們自己添加的菜單是無法被選擇的。
可
以想到,如果把菜單狀態(tài)設(shè)置為Enabled,并通過TPM_RETURNCMD標(biāo)志調(diào)用TrackPopupMenu或
TrackPopupMenuEx,再把返回的命令I(lǐng)D教給合適的窗口(如主框架窗口)去處理不就行了。關(guān)鍵點就在于如何把菜單狀態(tài)設(shè)置為
Enabled。
3) 實現(xiàn)
解決的辦法是截獲 WM_INITMENUPOPUP 消息,在菜單創(chuàng)建以后,尚未顯示之前修改菜單項狀態(tài),那瀏覽器就沒有辦法了。截獲WM_INITMENUPOPUP消息則可使用子類化(subclass)的技術(shù),前面通過IOleWindow接口我們得到了瀏覽器窗口的句柄hwnd,則可以這樣做:
HMENU g_hPubMenu = NULL;
WNDPROC g_lpPrevWndProc = NULL;
LRESULT CALLBACK CustomMenuWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
if (uMsg == WM_INITMENUPOPUP)
{
if (wParam == (WPARAM) g_hPubMenu)
{
::EnableMenuItem( 自定義的菜單命令I(lǐng)D, MF_ENABLED | MF_BYCOMMAND );
::CheckMenuItem( 自定義的菜單命令I(lǐng)D, MF_BYCOMMAND);
return 0;
}
}
return CallWindowProc(g_lpPrevWndProc, hwnd, uMsg, wParam, lParam);
}
HRESULT CMyHtmlView::OnShowContextMenu(DWORD dwID, LPPOINT ppt,
LPUNKNOWN pcmdtReserved, LPDISPATCH pdispReserved)
{
//瀏覽器菜單句柄保存在g_hPubMenu中
......
// subclass瀏覽器窗口
g_lpPrevWndProc = (WNDPROC)::SetWindowLong(hwnd, GWL_WNDPROC, (LONG)CustomMenuWndProc);
//m_SubclassWnd.SubclassWindow( hwnd );//MFC中用此方法更簡便
// Show shortcut menu
int iSelection = ::TrackPopupMenu(hSubMenu,
TPM_LEFTALIGN | TPM_RIGHTBUTTON | TPM_RETURNCMD,
ppt->x,
ppt->y,
0,
hwnd,
(RECT*)NULL);
// Unsubclass瀏覽器窗口
::SetWindowLong(hwnd, GWL_WNDPROC, (LONG)g_lpPrevWndProc);
g_lpPrevWndProc = NULL;
//m_SubclassWnd.UnsubclassWindow();
if (iSelection == 自定義的菜單命令I(lǐng)D )
{
::SendMessage( ::AfxGetMainWnd()->m_hWnd, WM_COMMAND, MAKEWPARAM(LOWORD(lResult), 0x0), 0 );
}
else
{
LRESULT lr = ::SendMessage(hwnd, WM_COMMAND, iSelection, NULL);
}
......
}
在MFC
中則更為方便,從CWnd繼承一個窗口類,假設(shè)為CWebBrowserSubclassWnd,為CMyHtmlView添加一個
CWebBrowserSubclassWnd類型的成員變量m_SubclassWnd,在子類化和去除子類化時調(diào)用
m_SubclassWnd.SubclassWindow( hwnd
)和m_SubclassWnd.UnsubclassWindow()即可。相應(yīng)的WM_INITMENUPOPUP消息處理函數(shù)如下所示:
void CWebBrowserSubclassWnd::OnInitMenuPopup(CMenu* pPopupMenu, UINT nIndex, BOOL bSysMenu)
{
CWnd::OnInitMenuPopup(pPopupMenu, nIndex, bSysMenu);
pPopupMenu->EnableMenuItem( 自定義的菜單命令I(lǐng)D, MF_ENABLED | MF_BYCOMMAND );
pPopupMenu->CheckMenuItem( 自定義的菜單命令I(lǐng)D, MF_BYCOMMAND);
}
下面的圖片顯示了將“文字大小”菜單項添加到“編碼”菜單項的下面的效果。

5、新的問題
看完上面的代碼,我們又自然地想到瀏覽器編程中的另一個問題,那就是“編碼”菜單。我們指定,手動建立一個“編碼”菜單是比較麻煩的事,而且很難做到與瀏覽器上下文菜單上的“編碼”菜單一樣的效果。何不使用上述的方法讓瀏覽器自己建立“編碼”菜單和處理相應(yīng)的命令呢?
具體實現(xiàn)請看下一篇文章《Internet Explorer 編程簡述(七)完美的“編碼”菜單》
參考資料 :
MSDN:Adding Entries to the Standard Context Menu
MSDN:How To Adding to the Standard Context Menus of the WebBrowser Control
MSDN:IDocHostUIHandler::ShowContextMenu Method
BeginThread.com:Custom WebBrowser Context Menus