Internet Explorer 编程简述(六)自定义浏览器上下文菜单

AI权益加码!Claude Code、Cursor等20+工具免费用! 购周边限时加赠Coding Plan Lite,畅享主流AI工具!学习进阶更高效! 阅读详情

关键字:WebBrowser, Internet Explorer, Custom Context Menu, ShowContextMenu, IDocHostUIHandler

1、概述
Internet Explorer提供了非常开发的接口,使开发人员不仅可以把其浏览器核心嵌入应用程序,还可以通过各种接口以实现更深层的控制。本文就将介绍对浏览器进行高级控制的话题之一——自定义上下文菜单。

2、最简单的情况
自定义的IE及WebBrowser的上下文菜单,最简单的方式就是在注册表的HKEY_CURRENT_USER/Software/Microsoft/Internet Explorer/MenuExt下添加自定义的键值,步骤如下:
1)添加一个新的键,其名称即为将来显示在上下文菜单中的菜单项名称,如:
 HKEY_CURRENT_USER/Software/Microsoft/Internet Explorer/MenuExt/&Google Search
2)将新增的键的默认值设置为一个包含脚本的网页的URL(或文件路径全名),该网页中的脚本将在用户点击上下文菜单中的“Google Search”后被浏览器执行。

3)在新增的键下还可以新建一个二进制值Contexts,用以指定我们新增的菜单项针对特定的网页对象是否出现,其取值可以是如下值的组合(逻辑或)

Context         Value
Default         0x1
Images          0x2
Controls        0x4
Tables          0x8
Text selection  0x10
Anchor          0x20

4)还可以建立一个DWORD类型的Flags项并将其值设置为0x01,这将使得前述脚本在一个模态窗口中执行,就好像是通过window.showModalDialog调用的,但不同的是在脚本中仍然可以访问window对象。
5)实例脚本如下:

通过修改注册表自定义菜单的方法适用于Internet Explorer和WebBrowser,也具有良好的扩展性。但我们如果希望执行的是不仅仅是脚本,二是自己的程序中代码,这种方法就不适用了。

3、使用完全自定义的菜单
1)
IDocHostUIhandler接口提供了一个ShowContextMenu方法,在需要显示上下文菜单之前,MSHTML引擎就会调用实现了IDocHostUIHandler接口的
宿主程序的ShowContextMenu方法。

HRESULTIDocHostUIHandler::ShowContextMenu(
    DWORD dwID,
    POINT *ppt,
    IUnknown *pcmdtReserved,
    IDispatch *pdispReserved
);

dwID参数的意义与Contexts的组合类似;ppt为菜单的弹出点屏 幕坐标;pcmdtReserved接口指向IOleCommandTarget接口,可用于检测网页对象的状态和执行命令等操作。 pdispReserved在IE5以上版本中指向的是网页对象的IDispatch接口,用以区分不同对象,比如我们可以这样来获得网页对象的指针:

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,则弹出默认的菜单。

2)实现
原理清楚之后,实现起来非常简单,和弹出一般的菜单没什么两样,举例如下,显示主框架的“文件菜单”:

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、自定义标准上下文菜单
1)
原理
更多的时候我们希望能在浏览器原来菜单的基础上作一些修改,如删掉“查看源文件”,添加自己的菜单项,等等,而不是完全不要原始的菜单,怎么办呢?借助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, //返回用户选择的菜单命令ID
    ppt->x,
    ppt->y,
    0,
    hwnd,
    (RECT*)NULL);
  // Send selected shortcut menu item command to shell
  LRESULT lr = ::SendMessage(hwnd, WM_COMMAND, iSelection, NULL); //发送到Internet Explorer_Server进行内部处理。
  FreeLibrary(hinstSHDOCLC);
  return S_OK;
}

从上面的例子我们看出,基本的方法就是根据“shdoclc.dll”文件 中的菜单资源建立菜单,再通过来自pcmdTarget的IOlcCommandTarget接口获得“编码”菜单以及 HKEY_CURRENT_USER/Software/Microsoft/Internet Explorer/MenuExt下的定义扩展菜单,然后以TPM_RETURNCMD标志调用TrackPopupMenu或 TrackPopupMenuEx弹出菜单,并将返回的菜单命令ID教给浏览器窗口进行处理。这种方法可以调用许多通过浏览器无法直接调用的命令和对话框 (参阅:《Internet Explorer 编程简述(五)调用IE隐藏的命令)。

所以,我们只需要在弹出菜单之前做一些自定义操作即可达到修改默认菜单的目的,如上面代码中就用删除了“查看源文件”菜单项。

2)问题
如果我们不仅仅是删除默认的菜单项或是修改了默认的菜单项,还添加了自己的菜单项,会出现什么情况呢?由于使用了类似于MFC中UpdateUI的机制,遇到不认识的CommandID,浏览器就会将其状态设置为Disabled,所以我们自己添加的菜单是无法被选择的。
可 以想到,如果把菜单状态设置为Enabled,并通过TPM_RETURNCMD标志调用TrackPopupMenu或 TrackPopupMenuEx,再把返回的命令ID教给合适的窗口(如主框架窗口)去处理不就行了。关键点就在于如何把菜单状态设置为 Enabled。

3)实现
解决的办法是截获WM_INITMENUPOPUP消息,在菜单创建以后,尚未显示之前修改菜单项状态,那浏览器就没有办法了。截获WM_INITMENUPOPUP消息则可使用子类化(subclass)的技术,前面通过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( 自定义的菜单命令ID, MF_ENABLED | MF_BYCOMMAND );
      ::CheckMenuItem( 自定义的菜单命令ID, 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 == 自定义的菜单命令ID )
{
  ::SendMessage( ::AfxGetMainWnd()->m_hWnd, WM_COMMAND, MAKEWPARAM(LOWORD(lResult), 0x0), 0 );
}
else
{
  LRESULT lr = ::SendMessage(hwnd, WM_COMMAND, iSelection, NULL);
}
......
}

在MFC中则更为方便,从CWnd继承一个窗口类,假设为 CWebBrowserSubclassWnd,为CMyHtmlView添加一个CWebBrowserSubclassWnd类型的成员变量 m_SubclassWnd,在子类化和去除子类化时调用m_SubclassWnd.SubclassWindow( hwnd )和m_SubclassWnd.UnsubclassWindow()即可。相应的WM_INITMENUPOPUP消息处理函数如下所示:

void CWebBrowserSubclassWnd::OnInitMenuPopup(CMenu* pPopupMenu, UINT nIndex, BOOL bSysMenu)
{
  CWnd::OnInitMenuPopup(pPopupMenu, nIndex, bSysMenu);

  pPopupMenu->EnableMenuItem( 自定义的菜单命令ID, MF_ENABLED | MF_BYCOMMAND );
  pPopupMenu->CheckMenuItem( 自定义的菜单命令ID, MF_BYCOMMAND);
}

下面的图片显示了将“文字大小”菜单项添加到“编码”菜单项的下面的效果。

5、新的问题
看完上面的代码,我们又自然地想到浏览器编程中的另一个问题,那就是“编码”菜单。我们指定,手动建立一个“编码”菜单是比较麻烦的事,而且很难做到与浏览器上下文菜单上的“编码”菜单一样的效果。何不使用上述的方法让浏览器自己建立“编码”菜单和处理相应的命令呢?

具体实现请看下一篇文章《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

引用地址Internet Explorer 编程简述(六)自定义浏览器上下文菜单

 
RecyclerView添加ContextMenu的两种方案详解 RecyclerView+ContextMenu实现的技术难点主要是在RecyclerView中获取被点击item的position,本文一共给出了两种解决方案。 阅读详情

相关推荐

Edge浏览器选择文字后迷你菜单开启方法详解

作为现代浏览器的重要功能之一,Edge的上下文菜单系统深刻影响着网页操作的便捷性。其中"选择文本时显示迷你菜单"功能,通过集成复制、搜索、翻译等高频操作按钮,显著提升了文字处理效率。本文将通过基础操作指南、进阶设置技巧、常见问题解析三个维度,系统讲解该功能的完整使用方案。该迷你菜单将传统右键菜单的8个常用功能浓缩为4个快捷按钮,采用浮动工具栏形式呈现。与传统右键菜单相比,迷你菜单的响应速度提升40%,操作路径缩短60%,特别适合需要频繁处理网页内容的开发者、研究人员和内容创作者。三、进阶设置与优化技巧。

nntxthml的博客 1029

IContextMenu添加桌面右键菜单

实现IContextMenu三个方法的bho!没有接触的这方面很有帮助,InvokeCommand,GetCommandString,QueryContextMenu

Internet Explorer 编程简述(十三)调用IE隐藏的命令(续)

   关键字:CGID_ShellDocView 1、概述在本系列五《调用IE隐藏的命令》中我们曾经从MSDN的一篇文章给出的ShowContextMenu范例入手,深入shdoclc.dll找到了藏于其中的浏览器上下文菜单资源,并以SendMessage发送WM_COMMAND消息到"Internet Explorer_Server"窗口以及其父窗口"Shell DocObjec

eagleboost's blog 1万+

IDocHostUIHandler::ShowContextMenu添加自定义菜单的另类实现

在很久很久以前(其实也就是IE6时代 - -),要实现在IWebBrowser2弹出原始的IE右键菜单,传统的思路是在ShowContextMenu加载SHDOCLC.DLL,读取菜单资源IDR_BROWSE_CONTEXT_MENU来获取菜单句柄,然后InsertMenu插入自己的菜单项 - -。在IE6.0以后的内核版本是没有提供SHDOCLC.DLL来让你加载的。   这里有两种另类的实

sgzwiz的专栏 2550

Vue实现右键菜单组件的详细教程,支持快捷键

本文将介绍如何使用Vue.js框架实现一个功能强大的右键菜单组件。通过该组件,你可以轻松地在你的Vue项目中添加自定义的右键菜单,提供更好的用户体验和操作便利性。本文将详细讲解组件的代码实现和使用方法

weixin_45357745的博客 4584

Internet Explorer 编程简述(七)完美的“编码”菜单

关键字:编码菜单, Encoding Menu, SHDVID_GETMIMECSETMENU 1、概述Internet Explorer有实在太多没有公布的东西。上一篇文章《Internet Explorer 编程简述自定义浏览器上下文菜单》提到的获取“编码”菜单的方法就是利用了浏览器的上层窗口“Shell DocObject View”的未公布的命令ID。本文将要介绍的是

eagleboost's blog 8975

Internet Explorer编程简述()

1、概述Internet Explorer提供了非常开发的接口,使开发人员不仅可以把其浏览器核心嵌入应用程序,还可以通过各种接口以实现更深层的控制。本文就将介绍对浏览器进行高级控制的话题之一——自定义上下文菜单。2、最简单的情况自定义的IE及WebBrowser上下文菜单,最简单的方式就是在注册表的HKEY_CURRENT_USER/Software/Microsoft/Internet Ex

The key to .Net 457

Internet Explorer 编程简述(九)在自己的浏览器中嵌入Google工具条

 关键字:Google Toolbar, Explorer Bars, ToolBands, IObjectWithSite, IDeskBand 1、概述  Internet Explorer强大而方便的可编程能力和可扩展能力为其抢占浏览器市场可谓是立下了汗马功劳。可编程主要体现两方面: 实现浏览功能的部分被包装成一个控件——WebBrowser Control,开

eagleboost's blog 1万+

Internet Explorer 编程简述(八)实现浏览历史菜单

 关键字:ITravelLogStg, IEnumTravelLogEntry, ITravelLogEntry 1、概述Internet Explorer的浏览历史菜单在4.0版本开始出现,但直到5.5之前,微软都未公布用于访问浏览历史的COM接口,如今已是IE6.0大行其道的年代,用于访问浏览历史的接口也早已公布多时,本文的目的则是试图抛砖引玉,简单介绍用于访问浏览历史的Tra

eagleboost's blog 1万+

如何使用BHO定制你的Internet Explorer浏览器

导读: 原文出处:Browser Helper Objects: The Browser the Way You Want It一、简介  有时,你可能需要一个定制版本的浏览器。在这种情况下,你可以自由地把一些新颖但又不标准的特征增加到一个浏览器上。结果,你最终有的只是一个新但不标准的浏览器。Web浏览器控件只是浏览器的分析引擎。这意味着仍然存在若干的与用户接口相关的工作等待你做――增加一

xu的blog 1645

IE编程精选-如何使用BHO定制你的Internet Explorer浏览器

IE编程精选 摘自:http://www.learnsky.com/bbs/printpage.asp?BoardID=6&ID=1002如何使用BHO定制你的Internet Explorer浏览器原文:微软公司 Dino Esposito编译:朱先中原文出处:Browser Helper Objects: The Browser the Way You Want It一、简介  有

dtren的专栏 2653

()如何使用BHO定制你的Internet Explorer浏览器

如何使用BHO定制你的Internet Explorer浏览器 分享到:新浪微博腾讯微博更多0 收藏 发布日期: 2005-03-30 15:19 浏览次数: 1535次 标  签: COM 文章评分: 5.0 操  作: 打印 DinoEsposito 称号:未设置 简介:未设置

gxj1680的专栏 1467
上一篇: 对话框的数据交换,对里面的函数实现比较详细
下一篇: 1个月的VC小结
skyremember
博客等级 码龄20年 203粉丝 484原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值