Saturday, January 08, 2005

 

IE menus

1.
How To Disable the Default Pop-up Menu for CHtmlView
This article was previously published under Q236312
http://support.microsoft.com/default.aspx?scid=kb;EN-US;Q236312

2.
屏蔽CHtmlView或WebBrower控件右键菜单技巧合集

作者:kingcom_xu
出处:http://www.csdn.net/develop/article/18/18541.shtm

2.1.常被人鄙视的方法(PreTranslateMessage)

经常见到有人问怎么屏蔽html的右键菜单,有人答用PreTranslateMessage函数拦截wm_rbuttondown消息,于是总会有人说这种方法怎么烂,@_@,我真不知道为什么,不过我想这也是一种方法呀,而且非常简单,所以还是列出来吧:)

BOOL CPreTranslateMsgView::PreTranslateMessage(MSG* pMsg)
{
if ((pMsg->message == WM_RBUTTONDOWN)||(pMsg->message == WM_RBUTTONDBLCLK)){
CPoint point(pMsg->pt);
ScreenToClient(&point);

IHTMLDocument2* pdoc2=NULL;
IHTMLElement* pElement=NULL;
IDispatch* pDisp=NULL;
pDisp=GetHtmlDocument();
pDisp->QueryInterface(IID_IHTMLDocument2,(void**)&pdoc2);
pDisp->Release();

pdoc2->elementFromPoint(point.x,point.y,&pElement);
pdoc2->Release();
if(pElement){
BSTR ID;
pElement->get_id(&ID);
pElement->Release();
CString str=(LPCTSTR)(_bstr_t)ID;
if(str=="Layer1"){
CMenu menu;
menu.LoadMenu(IDR_MENU1 );
CMenu* pmenu=menu.GetSubMenu(0);
pmenu->TrackPopupMenu(0,pMsg->pt.x,pMsg->pt.y,this);
}
}
return TRUE;//如果想完全屏蔽掉,不显示任何菜单,直接返回TRUE就行,上面这些代码演示了怎么对html中特定ID的元素弹出自己想要显示的菜单
}else
return CHtmlView::PreTranslateMessage(pMsg);
}

2.2.通过子类化IE控件的窗口并处理WM_CONTEXTMENU消息(按:以下为111222翻译的MSDN文档)

在看这篇文章之前你首先要了解IE控件、CHtmlView。
包罗的CHtmlCtrl类由CHtmlView派生而来,其利用CHtmlView的LoadFromResource函数从资源中打开网页。

问题1:我很喜欢你的 CHtmlCtrl类,那正是我所需要做的。但有一点我想知道,能够禁止用户右键网页时候弹出菜单么?我不想用户看到我的HTML源文件。我重载了CHtmlCtrl的WM_CONTEXTMENU消息映射,结果没什么用处。

问题2:我想禁止CHtmlView或者CWebBroser2弹出鼠标右键菜单,我重载了WM_RBUTTONDOWN的消息映射函数,但不行,请帮助!

回答:很高兴你们喜欢我的CHtmlCtrl类,在当时我没有考虑到这个不受欢迎的右键菜单,现在我把禁止右键菜单的简便方法指出:

只需要一行----在你的HTML里面这样写BODY
BODY oncontextmenu="return false">

它将告诉浏览器:嘿,别显示另人讨厌的右键菜单!

像下面这样你可以构造自己的菜单:

oncontextmenu="ShowMyMenu(); return false"

ShowMyMenu可以是一段JAVASCRIPT,用来创建自己的个性化菜单。

但我们这里是对C++的谈论,大谈JAVASCRIPT有些不适合,那么------让我用C++实现这个,官方提供的方法是利用IDocHostUIHandler接口(在http://www.codeguru.com/有利用IDocHostUIHandler禁止CHtmlView右键菜单的范例) ,但是这样做太麻烦了,而这对不懂COM的程序员将是再痛苦不过的经历!

你们的想法是重载WM_CONTEXTMENU 或WM_RBUTTONDOWN的消息映射,的确,大部分窗口是他们。但这里不行。
原因是:CHtmlView、CWebBrowser2不是真正的输入窗口,这有着很多我们肉眼没有看见的窗口。为了看到这些窗口,我们打开VC++的窗口工具spy++看个究竟。

Dialog
AfxFrameOrView42d // CHtmlCtrl
Shell Embedding
Shell DocObject View
Internet Explorer_Server

如图:浏览窗口中有三个父/子窗口在真正的输入窗口上面,如此说来 WM_CONTEXTMENU 和 WM_RBUTTONDOWN消息能被输入窗口获得么?

Internet Explorer_Server 窗口接受到了输入,这意味着你想处理WM_CONTEXTMENU就必须通过subclass来处理这个窗口的消息。
为此我们必须知道Internet Explorer_Server的句柄。

这有许多办法获得 Internet Explorer_Server 的句柄,FindWindow 不会有效果,因为他只能找到顶层窗口(111222注:笔者没有想到FindWindowEx,通过Ex函数可以找到任何窗口)。下面是我写的用来获得该窗口的函数,利用GetWindow
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 是IE控件、CHtmlView唯一的子窗口,所以我们利用GetLastChild找到的句柄是准确的。

下面我们重载WM_CONTEXTMENU的消息映射:

class CMyIEWnd : public CWnd {
public:
afx_msg void OnContextMenu(CWnd* pWnd, CPoint pos) { }
DECLARE_MESSAGE_MAP();
};

WM_CONTEXTMENU的消息影射什么也不做,OnContextMenu 是一个空函数时候将不显示菜单。
下面应用这个CMyIEWnd,我把它作为成员变量加到CMyHtmlCtrl之中:

class CMyHtmlCtrl : public CHtmlCtrl {
protected:
CMyIEWnd m_myIEWnd;
};

上面所有的一切都必须在通过subclass钩取 Internet Explorer_Server 的窗口之后才能有效,那么我们在哪做呢?
随便那都可以,下面我们是网页浏览完毕后禁止右键菜单。

void CMyHtmlCtrl::OnNavigateComplete2(LPCTSTR strURL)
{
if (!m_myIEWnd.m_hWnd) {
HWND hwnd = GetLastChild(m_hWnd);
m_myIEWnd.SubclassWindow(hwnd);
}
}

OK~~所有的工作都完成了,下面我来讲解一下它的实际过程:当用户打开了关于对话框,该对话框创建CHtmlCtrl窗口打开HTML文档。在打开文档完毕后将发送它将触动OnNavigateComplete2,CMyHtmlCtrl::OnNavigateComplete2 此时将会找到Internet Explorer_Server ,并且通过SubclassWindow将其一些消息处理都归谬于CMyIEWnd ,CMyIEWnd 的 WM_CONTEXTMENU此时毫无作为,最终右键菜单不见了。

必须指出的是:Internet Explorer_Server 窗口句柄经常性改变,所以我们必须为显示HTML的关于对话框做更多的东西。
你必须有效的用unsubclass 和 resubclass。
至此用非官方方法禁止CWebBrowser控件\CHtmlView右键菜单的方法介绍完毕。

(按:有朋友指出用这种方法仅仅屏蔽鼠标右键是不够的,因为用键盘也可以调出来这个contentmenu。那么把SubClassWindow的WM_KEYDOWN干掉就得了。


2.3.利用IDocHostUIHandler接口(官方提供的方法)
VC.net中的CDHtmlDialog就是这种方法了,想自己实现的可以参考这篇文章:
在对话框中显示网页,并屏蔽掉IE的弹出式菜单 wuya(原作)
http://www.csdn.net/develop/Read_Article.asp?Id=8813

2.4.利用COM的连接点机制,直接处理html元素的事件

我们先看看在VB中想屏蔽掉右键菜单是怎么做的:

Dim WithEvents m_doc As HTMLDocument

Private Function m_doc_oncontextmenu() As Boolean
m_doc_oncontextmenu = False
End Function

Private Sub WebBrowser1_DownloadComplete()
Set m_doc = WebBrowser1.Document
End Sub

呵呵,非常简单吧?处理HTMLDocument的oncontextmenu事件,视具体需要弹出自己的菜单,然后返回true或false就可以了。
同理,我们在VC中如果能响应html页面元素对象的事件并做出处理的话也可以达到屏蔽右键菜单的目的了,在VB中响应COM的事件被封装起来,用一个WithEvents关键字就轻松搞定,在VC中怎么实现??
我们知道COM是通过连接点机制来实现事件的,VC中你得在程序中实现一个特定的接口,并把这个接口的指针通过一定的途径传送给COM对象并通知它需要订阅该对象的消息。

下面通过处理HTMLDocument2的oncontextmenu事件来演示具体怎么在MFC程序中实现屏蔽掉右键菜单,同理你可以处理其它的像DIV,Button等元素的事件来制定相应的右键菜单。
通过查阅MSDN,可以知道要响应HTMLDocument2的事件必须在程序中实现HTMLDocumentEvents2接口,这个接口是Dispinterface类型的,而在MFC想实现一个IDispatch接口就简单的就是从CCmdTarget类派生一个新类并用DECLARE_DISPATCH_MAP,BEGIN_DISPATCH_MAP,DISP_FUNCTION_ID,END_DISPATCH_MAP等宏来添加接口函数,由于CHtmlView或是CDialog都是间接派生自CCmdTarget的,所以直接在这些类上实现这个接口就可以了,以对话框加IE控件为例(稍微改动可用于CHtmlView),实现如下:

1.新建基于对话框工程命名为HtmlDemoDialog,在主对话框中加入Microsoft Web Browser控件并生成包装类等。
2.在主对话框类头文件CHtmlDemoDialogDlg.h中加入
#include //定义了IHTMLDocument2等接口
#include //定义了HTMLDocumentEvents2接口的方法DISPID

class CCHtmlDemoDialogDlg : public CDialog
{
...
DECLARE_DISPATCH_MAP()//声明dispatch map表

public:
BOOL onHtmlContextMenu(IHTMLEventObj *pEvtObj);
//事件处理函数,原型可以参考MSDN中关于HTMLDocumentEvents2的说明
DWORD m_dwCookie;
//用于标记一个连接点
IHTMLDocument2* pDoc2;
//想要处理事件的COM对象的指针
...
}
2.在对话框类实现文件CHtmlDemoDialogDlg.cpp中加入
#include //定义了AfxConnectionAdvise、AfxConnectionUnadvise等函数,等会连接到事件源时要用到
...
//填充dispatch map表,以供Invoke()调用
BEGIN_DISPATCH_MAP(CCHtmlDemoDialogDlg, CDialog)
DISP_FUNCTION_ID(CCHtmlDemoDialogDlg, "oncontextmenu", DISPID_HTMLDOCUMENTEVENTS2_ONCONTEXTMENU, onHtmlContextMenu, VT_BOOL, VTS_DISPATCH)
END_DISPATCH_MAP()
...

CWebbrowserDlg::CWebbrowserDlg(CWnd* pParent /*=NULL*/)
: CDialog(CWebbrowserDlg::IDD, pParent)
{
EnableAutomation();//必须有,否则等会用GetIDispatch()时会失败.
...
}

BOOL CCHtmlDemoDialogDlg::onHtmlContextMenu(IHTMLEventObj *pEvtObj)
{
//在成功连接上事件源后,每次用户右击都会调用这个函数,你可以根据pEvtObj来判断当前光标位置等,然后决定是自己弹出菜单,让IE弹出菜单,还是什么都不做...
return FALSE;
}

void CCHtmlDemoDialogDlg::OnDocumentCompleteExplorer1(LPDISPATCH pDisp, VARIANT FAR* URL)
{
//处理WebBrowser控件的DocumentComplete事件,并初始化pDoc2指针和连接到事件源
HRESULT hr=m_wb.GetDocument()->QueryInterface(IID_IHTMLDocument2,(void**)&pDoc2);
BOOL Ret = AfxConnectionAdvise(
pDoc2, //可连接对象的接口指针
DIID_HTMLDocumentEvents2, //连接接口ID
GetIDispatch(FALSE), //把内嵌的IDispatch实现类的一个对象实例m_xDispatch传了出去
FALSE, //donod addref
&m_dwCookie ); //cookie to break connection later...
if(Ret){
AfxMessageBox("成功挂接上");
}
}
3.到这里,基本步骤都以完成,运行后如果没有什么灾难发生的话可以看到"成功挂接上"的消息框,并且在IE控件中点击右键不会弹出菜单,断开事件连接的代码如下:
AfxConnectionUnadvise(pDoc2,
DIID_HTMLDocumentEvents2 ,
GetIDispatch(FALSE),
FALSE,
m_dwCookie );
其余的善后工作交给你去处理了.

3.
自定义IE右键只要看看
http://msdn.microsoft.com/library/default.asp?url=/workshop/browser/ext/tutorials/context.asp

http://support.microsoft.com/support/kb/articles/q177/2/41.asp&NoWebContent=1
就很清楚啦,和Band其实也是大同小异。

更加好玩的IE右键还有很多,比如ContextMenu (ASP.NET)
http://www.gotdotnet.com/Community/UserSamples/Details.aspx?
SampleGuid=826a8f40-f45a-46fa-8477-19efd144c9bc
代码和使用都很简洁,效果也不错

4.
补充:
如何在Macintosh IE中实现Contextual Menu
作者:wlfjck
出处:http://blog.ifthen.net/archives/2003_09.html
Macintosh操作系统中有这样一个概念,用户可以定制Contextual Menu,就像Windows中,用户通过鼠标右键弹出的快捷菜单一样。

最近,我开发的一个项目需要实现在Macintosh系统的浏览器中添加一个Contextual Menu菜单项。Macintosh系统中,现有的浏览器如下:
1.Internet Explorer 5.2,微软开发,使用Carbon技术。
2.Netscape系列,包括Mozilla,Mozilla.org开发,使用Carbon技术。
3.基于Mozilla技术的Camino,版本0.7,Mozilla.org开发,使用Cocoa技术。
4.Safari 1.0,苹果公司开发,现在是苹果OSX 10.2以上系统中最牛的浏览器,使用Cocoa技术。
5.OmniWeb,老字号的苹果浏览器,omnigroup.com开发,使用Cocoa技术。
6.Opera,Opera.com开发,使用Carbon技术。

浏览器对Contextual Menu的支持与否,关键在于其程序内部是否使用了ContextualMenuSelect,这个API调用,可以从developer.apple.com中查到。举例来说,Eudora程序在其内部使用了ContextualMenuSelect,所以它支持Contextual Menu的显现。但是对于浏览器,我没有源代码,无法判断其是否支持Contextual Menu的显现。

这个问题是这样解决的,我使用Contextual Menu Workshop 1.5,这是一个编写Contextual Menu的开发包,可以从以下地址下载,http://free.abracode.com/cmworkshop/,源代码是公开的。首先,我用Codewarrior 8.3编译其中的EmptyCM项目,这是一个空的Contextual Menu项目,它不对自己运行的上下文做任何的检测,所以用它来检测浏览器是否调用了ContextualMenuSelect非常有用。如果浏览器调用了,EmptyCM的回调函数就会被调用。

将编译好的EmptyCM.plugin拷贝到 ~/Library/Contextual Menu Items,顺便说一句,Cotextual Menu属于一种Plugin,它必须放到指定的地点才能起作用,有两个地方可以存放,一个是~/Library/Contextual Menu Items,这样的话,它只对当前用户起作用,另外一个是/Library/Contextual Menu Items,这个目录对所有的用户起作用。依次运行各个浏览器,在浏览器中调出右键Contextual Menu,最后我发现只有IE支持Contextual Menu接口。这里面有一个有趣的现象,所有使用Cocoa开发的浏览器,也会出现EmptyCM的Contextual Menu,但是实际上,他们是不支持的,这是由于Cocoa底层做了对Contextual Menu的调用,所以它们才会显现Contextual Menu。从回调函数的AEDesc* inContext中可以看出,其实inContext.datahandle是null。

好,接下来,我需要开发IE的Contextual Menu了。步骤如下:
1.复制EmptyCM。
2.生成我自己的UUID,这个概念和Windows上的COM中的UUID是一样的,Contextual Menu Plugin也需要一个唯一标示。
3.替换Info.plist中的UUID为我自己的UUID。
其他步骤不多说了,可以从Contextual Menu Workshop的Documentation.txt文档中查到。

现在剩下的问题是,IE从回调接口中传给我什么东西,我怎样才能取得所需的URL,也就说,我右键点击浏览器上的URL后,我怎样才能得到这个URL的地址,IE没有告诉我,google没有告诉我,微软的MAC Support也没有告诉我,新闻组也没有告诉我。所有的这些,我只能自己来分析。

以下是我的分析结果:
1.首先调用AECountItems(inContext,&Count),取得AEDesc(IE传回的是一个'list'类型)有几个'reco'类型的纪录。
2.用for循环调用AEGetNthDesc依次取得相应的子纪录。
3.到这一步,基本上就没有办法了,因为,下面的纪录格式我无法知道。
4.我调用AEGetDescDataSize,取得这个AEDesc的大小,分配相应大小的Buffer,然后调用AEGetDescData,将这个纪录的内存内容取回到Buffer中。
5.在调试器中,查看Buffer的内容,果然,猜的没有错,URL果然在这里面。
6.剩下来的,就是对这个内存内容进行格式分析了,我把结果写在下面

内存增长方向由低到高
struct ierecord
{
unsigned int dle2; // 固定内容,内容为'dle2'
unsigned int pad1; // 填充内容,内容全为0
unsigned int reco1; // 固定内容,内容为'reco'
unsigned int size1; // 余下部分的长度,不包括自身
unsigned int pad2; // 填充内容,内容全为0
unsigned int pad3; // 填充内容,内容全为0
unsigned int c24; // 固定内容,内容为0x18
unsigned int reco2; // 固定内容,内容为'reco'
unsigned int c2; // 固定内容,内容为0x02
unsigned int pad4; // 填充内容,内容全为0
unsigned int name; // 固定内容,内容为'Name'
unsigned int text1; // 固定内容,内容为'TEXT'
unsigned int size2; // Name字符串的长度,不包括自身
char namestr[var1]; // Name字符串,var1 = size2
char end1; // Name字符串结束符,内容为'\0'
// 2003年10月13日更新,如果size2的长度可为2整除的话,就没有end1这一个字段。
unsigned int url; // 固定内容,内容为'URL '
unsigned int text2; // 固定内容,内容为'TEXT'
unsigned int size3; // URL字符串的长度,不包括自身
char namestr[var2]; // URL字符串,var2 = size3
char end2; // URL字符串结束符,内容为'\0'
}

7.我只对URL字符串感兴趣,并且,由于字符串的结尾是用'\0'结束的,符合C语言字符串标准。因此算法如下:
a.判断Buffer的前四个字符是否为'dle2',这个是IE的标示符。
b.另char* position = buffer + 48,因为Buffer前48个字节没有用处,position +=*(unsigned int*)position + 4 + 1 +12,这时position指向的就是URL字符串了。
3/22/2004修改

补充
By 挖掘者
From http://www.cnblogs.com/ausoldier/archive/2005/01/20/94453.html

屏蔽IE右键的方法:只需将这册表中:Software\\Policies\\Microsoft\\Internet Explorer\\Restrictions项中的NoBrowserContextMenu设置为1
HKEY hk;
if (RegCreateKey(HKEY_CURRENT_USER, "Software\\Policies\\Microsoft\\Internet Explorer", &hk))
::MessageBox(NULL, "Could not create the registry key.", "", MB_OK);


if (RegCreateKey(HKEY_CURRENT_USER, "Software\\Policies\\Microsoft\\Internet Explorer\\Restrictions", &hk))
::MessageBox(NULL, "Could not create the registry key.", "", MB_OK);
RegCloseKey(hk);

RootKey=HKEY_CURRENT_USER; //注册表主键名称
SubKey="Software\\Policies\\Microsoft\\Internet Explorer\\Restrictions"; //欲打开注册表值的地址
ValueName="NoBrowserContextMenu"; //欲设置值的名称
SetContent_D[0]=1; //值的内容

if((SetValue_D(RootKey,SubKey,ValueName,SetContent_D))!=0)
AfxMessageBox("操作失败!");

其中int SetValue_D (struct HKEY__*ReRootKey,TCHAR *ReSubKey,TCHAR *ReValueName,int ReSetContent_D[256])是设置DWORD值函数:
SetValue_D (struct HKEY__*ReRootKey,TCHAR *ReSubKey,TCHAR *ReValueName,int ReSetContent_D[256])
{
int i=0; //操作结果:0==succeed
if(RegOpenKeyEx(ReRootKey,ReSubKey,0,KEY_WRITE,&hKey)==ERROR_SUCCESS)
{
if(RegSetValueEx(hKey,ReValueName,NULL,REG_DWORD,(const unsigned char *)ReSetContent_D,4)!=ERROR_SUCCESS)
{
AfxMessageBox("错误:无法设置有关的注册表信息");
i=1;
}
RegCloseKey(hKey);
}
else
{
AfxMessageBox("错误:无法查询有关的注册表信息");
i=1;
}
return i;
}

好狠
1/19/2005



<< Home

This page is powered by Blogger. Isn't yours?