提出问题: VC知识库在线杂志第六期有一篇文章VC6中使用CHtmlView在对话框控制中显示HTML文件,很多读者来信说很喜欢这种功能。
但是美中不足的是在对话框的HTML页面上单击鼠标右键会弹出上下文菜单。
从而可以象在IE中那样看到页面的源代码。
为了防止用户查看HTML代码,有人尝试过在CHtmlCtrl派生的窗口中重载WM_CONTEXTMENU,或者在CHtmlView以及CHtmlCtrl类中禁用右键的上下文菜单和弹出式菜单,这两个方法都没有成功。
那么如何禁用HTML的这个上下文菜单呢? 本文就针对这个问题用不同的方法来完善上次的程序。
解答: CHtmlCtrl类可以将CHtmlView转换成在任何窗口中使用的控制。
我用它写了一个程序叫AboutHtml,此程序实现了一个HTML对话框。
但疏忽了鼠标右键的上下文菜单,所以在HTML对话框中单击鼠标右键,会弹出标准的浏览器上下文菜单(如图一),而这个菜单对于某些人来说可能是多余的。
图一 不想要的上下文菜单 其实,要解决这个问题有一个非常简单的办法,真是易如反掌,甚至不用写任何C 代码!只要在HTML页面中加一行指令即可: // // 这条指令告诉浏览器不要显示上下文菜单。
也可以象下面这样写: // 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,我这么做了。
图二在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)定制它!否则后果不堪设想。