如何禁用HTML页面的上下文菜单

时间:2024-03-31 13:05:11
提出问题:
VC知识库《在线杂志》第六期有一篇文章“VC6中使用CHtmlView在对话框控制中显示HTML文件”,很多读者来信说很喜欢这种功能。但是美中不足的是在对话框的HTML页面上单击鼠标右键会弹出上下文菜单。从而可以象在IE中那样看到页面的源代码。为了防止用户查看HTML代码,有人尝试过在CHtmlCtrl派生的窗口中重载WM_CONTEXTMENU,或者在CHtmlView以及CHtmlCtrl类中禁用右键的上下文菜单和弹出式菜单,这两个方法都没有成功。那么如何禁用HTML的这个上下文菜单呢? 本文就针对这个问题用不同的方法来完善上次的程序。
解答:
CHtmlCtrl类可以将CHtmlView转换成在任何窗口中使用的控制。我用它写了一个程序叫AboutHtml,此程序实现了一个HTML对话框。但疏忽了鼠标右键的上下文菜单,所以在HTML对话框中单击鼠标右键,会弹出标准的浏览器上下文菜单(如图一),而这个菜单对于某些人来说可能是多余的。

如何禁用HTML页面的上下文菜单

图一 不想要的上下文菜单
其实,要解决这个问题有一个非常简单的办法,真是易如反掌,甚至不用写任何C++代码!只要在HTML页面中加一行指令即可:
//<BODY oncontextmenu="return false">//

这条指令告诉浏览器不要显示上下文菜单。也可以象下面这样写:
//oncontextmenu="ShowMyMenu(); return false"//
ShowMyMenu是一个显示定制菜单的JavaScript过程。本文例子代码之一AboutHtml1使用的就是oncontextmenu。源代码可以从本文的开始处下载。
由于VC知识库是一个关于C++以及Visual C++的网站,与JavaScript之类的脚本语言没什么关系。所以我们要用另一种稍微复杂一点的方法来实现相同的事情,那就是用C++来做。为此,正规的C++方法是实现IDocHostUIHandler接口,而且要做的事情很多。至于为什么要实现它,请参见有关文档。用WM_CONTEXTMENU 或者 WM_RBUTTONDOWN来处理这个问题的思路的确是通常Windows做事情的方式。但是问题是CHtmlCtrl窗口不是真正的输入窗口。窗口有很多种,只要用Spy++工具看一下我们的例子程序就知道在你眼前会出现多少种窗口。如图二所示,在实际的输入窗口上,浏览器窗口有三级父/子窗口。
Dialog AfxFrameOrView42d // CHtmlCtrl Shell Embedding Shell DocObject View Internet Explorer_Server
它是个接收输入的Internet Explorer_Server服务器窗口,并且如果你想要截获WM_CONTEXTMENU消息,必须子类化这个窗口。在MFC中,这意味着你必须获取HWND并调用SubclassWindow。记住了,这是一种非常规方式,而且微软的那帮家伙也明确禁止这样做,不过我还是根据原来的程序写了另一个版本AboutHtml2,我这么做了。
如何禁用HTML页面的上下文菜单
图二在Spy++中的父/子关系
获得这个神秘的Internet Explorer_Server HWND的方法有很多种。但FindWindow不行,因为它只能得到顶层窗口。由于此服务器窗口是浏览器的曾孙(great-grandchild),在所有层次上都没有同胞兄弟,所以下列算法成立:
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窗口。一旦取得HWND,剩下的事情便是写一个新的MFC类对它进行子类化。
class CMyIEWnd : public CWnd {
public:
   afx_msg void OnContextMenu(CWnd* pWnd, CPoint pos) { }
   DECLARE_MESSAGE_MAP();
};

这个类重载WM_CONTEXTMENU,其它什么事情也不做:OnContextMenu是个空函数,返回的东西不显示菜单,也不调用基类(CWnd)的方法。使用CMyIEWnd时,在CMyHtmlCtrl中添加一个实例:
//class CMyHtmlCtrl : public CHtmlCtrl {protected:   CMyIEWnd m_myIEWnd;};//

把这一切联系在一起的最关键的一步是调用SubclassWindow。但在哪里调用以及什么时候调用呢?最好时机是在浏览器加载页面之后。
void CMyHtmlCtrl::OnNavigateComplete2(LPCTSTR strURL)
{
   if (!m_myIEWnd.m_hWnd) {
      HWND hwnd = GetLastChild(m_hWnd);
      m_myIEWnd.SubclassWindow(hwnd);
   }
}

具体处理过程是这样的:当用户打开“关于”对话框,对话框创建CHtmlCtrl窗口来打开文档,当浏览器将文档打开以后,它发送一个通知,MFC将这个通知定向到OnNavigateComplete2。CMyHtmlCtrl::OnNavigateComplete2调用GetLastChild来获得“真正的”输入窗口并将它子类化。这时所有的消息将通过CMyIEWnd类去往Internet Explorer_Server,包括WM_CONTEXTMENU。这里要注意,IE的HWND是可以修改的,所以如果除了“关于”对话框外,你还想做一些其它的事情的话,必须要对HWND进行反子类化(unsubclass)和重子类化(resubclass)处理。
使用这个技术有两个重要事情需要注意。第一,它功能很强,因为你子类化了“真正的”IE窗口,你可以做几乎任何事情。第二,如果你不小心而使用不当,那将会发生最糟糕最糟糕的事情。一旦你用这种方法控制了资源管理器窗口,等于是把所有赌注放进去了。记住不要用不正当的方式去玩弄浏览器,而是要通过正式接口(IDocHostUIHandler)定制它!否则后果不堪设想。