??xml version="1.0" encoding="utf-8" standalone="yes"?>
作者:(x)杨老师
下蝲源代?/font> Band 样式 lgcd CATID
关键?/strong>QBandQDesk BandQExplorer BandQTool BandQ浏览器栏,工具栏,桌面工具?br>
一、引a
最q,׃工作的要求,我需要在 IE 上做一些开发工作。于是在 MSDN 上翻阅了一些资料,Ҏ(gu) MSDN 上的说明我用 ATL 胜利完成?#8220;资本家老板”分配的Q务?br>Qƈ且在白天睡觉的过E中梦到了老板l我加工资啦......Q?br>现在Q我?MSDN 上的原文资料Q经q翻译整理ƈ把一?ATL 的实现奉贤给 VCKBASE 上的朋友们?/p>
基本band 对象
必须实现?COM 接口
IPersistStream
IObjectWithSite
IDeskBand、IDockingWindow、IOleWindow
选择实现?COM 接口
Band 对象注册
在翻译的q程中,有两个词汇非怸好理解。第一个词?Band 对象Q词怸译?#8220;镶边、裙子边、带子、乐?.....”我的英文水^有限Q实在不知道应该译Z么词汇更合适。于是我毅然决然地决定:(x)在如下的中,依然使用 band q个词!Q什么?没听明白Q我的意思就是说Q我不翻译这个词了)但到?Band 对象应该如何理解那?L(fng)图一Q?br>
图一
图一中画U圈的地方,分别UC“垂直的浏览器?#8221;?#8220;水^的浏览器?#8221;?#8220;工具?#8221;?#8220;桌面工具?#8221;。这?#8220;?#8221;Q都可以?IE ?#8220;查看”菜单中或鼠标右键的上下文快捷方式菜单中显C或隐藏h。这些界面窗口的实现Q其实就是实CU?COM 接口对象Q而这个对象叫 band。这个概念实在是只能意会(x)而无法言传的Q我M能在文章中把它翻译ؓ(f)“L靠在 IE ȝ口边上的对象”吧?^_^
另外Q还有一个词?site。这个很好翻译,?#8220;站点”Q。呵呵,我敢打包,如果你要能理解这个翻译在计算机类文章中的含义Q那只能恭喜你了,你的智慧太高了。(都是学计机软g的hQ做人的差距咋就q么大呢Q)在本文章中Qsite 可以q样理解QIE 的主框架四周Q就好比?#8220;汽R?#8221;Q那?band 对象Q就好比?#8220;汽R”。band 汽RL可以停靠?#8220;汽R?#8221;上。所以,site 是“站点”Q它也是 COM 接口的对象(IObjectWithSite、IInputObjectSiteQ?br>
3.1 基本 band 对象
Band 对象Q从 Shell 4.71(IE 5.0) 开始提供支持。Band 是一?COM 对象Q必L在一个容器中M用,当然使用它们好象用普通窗口是一L(fng)。IE 是一个容器,桌面 Shell 也是一个容器,它们提供不同的函数功能,但基本的实现是相似的?br> Band 对象分三U类型,览器栏 bandQExplorer bandsQ、工h bandQTool BandsQ和桌面工具?Desk bands)Q而浏览器?band 又有两种表现形式Q垂直和水^的。那?IE ?Shell 如何区分q加载这?bands 对象呢?Ҏ(gu)是:(x)你要对不同的 band 对象Q在注册表中注册不同的组件类型(CATIDQ?br>
垂直的浏览器?/td>
CATID_InfoBand
00021493-0000-0000-C000-000000000046
水^的浏览器?/td>
CATID_CommBand
00021494-0000-0000-C000-000000000046
桌面的工h
CATID_DeskBand
00021492-0000-0000-C000-000000000046
IE 工具栏不使用lgcd注册Q而是使用在注册进?CLSID 的登记方式。详l情况见 3.3?br> 在例子程序中Q实C全部四个cd?band 对象Q垂直浏览器?CVerticalBar)昄了一?HTML 文gQƈ且实C?IE ȝ口浏览网늚D{功能;水^的浏览器?CHorizontalBar)是一个编辑窗Q它同步昄当前|页?BODY 源文件内容;IE 工具?CToolBar)最单,只是d了一个空的工hQ桌面工h(CDeskBar)实现了一个单行编辑窗口,你可以在上面输入命o(h)行或文g名称Q回车后它会(x)执行 Shell 的打开动作?br>
3.2 必须实现?COM 接口
Band 对象?IE ?Shell 的进E内服务器,所以它被包装在 DLL 中。而作?COM 对象Q它必须要实?IUnknown ?IClassFactory 接口。(大家可以不同操心Q因为我们用 ATL 写程序,q两个接口是不用我们自己写代码的。)另外QBand 对象q必d?IDeskBand、IObjectWithSite ?IPersistStream 三个接口Q?br> IPersistStream 是持l性接口的一U。当 IE 加蝲 band 对象的时候,它通过q个接口?Load Ҏ(gu)传递属性值给对象Q让其进行初始化Q而当卸蝲前,IE 则调用这个接口的 Save Ҏ(gu)保存对象的属性。用 ATL 实现q个接口很简单:(x)
class ATL_NO_VTABLE Cxxx : ...... public IPersistStreamInitImpl, // dl承 ......{public: BOOL m_bRequiresSave; // IPersistStreamInitImpl 所必须的变?.....BEGIN_COM_MAP(CVerticalBar) ...... COM_INTERFACE_ENTRY2(IPersist, IPersistStreamInit) COM_INTERFACE_ENTRY2(IPersistStream, IPersistStreamInit) COM_INTERFACE_ENTRY(IPersistStreamInit) ......END_COM_MAP()BEGIN_PROP_MAP(Cxxx)...... // d需要持l性的属性END_PROP_MAP()上面的代码,其实实现的是 IPersistStreamInit 接口Q不q没有关p,因ؓ(f) IPersistStreamInit z?IPersistStreamQ实例化了派生类Q自然就实例化了基类。在例子E序中,我只在桌面工h对象中添加了持箋性属性,用来保存和初始化“命o(h)?#8221;。另?COM_INTERFACE_ENTRY2(AQB)表示的含义是Q如果想查询A接口的指针,则提供B接口指针来代ѝؓ(f)什么可以这样那Q因为B接口z自A接口Q那么B接口的前几个函数必然是A接口的函CQ自然B接口的地址其实和A接口的地址是一L(fng)了?br> IObjectWithSite ?IE 用来Ҏ(gu)件进行管理和通讯用的一个接口。必要实现q个接口?个函敎ͼ(x)SetSite() ?GetSite()。当 IE 加蝲 band 对象和释?band 对象的时候,都要调用 SetSite()函数Q那么在q个函数里正好是写初始化和释放操作代码的地方Q?
STDMETHODIMP Cxxx::SetSite(IUnknown *pUnkSite){ if( NULL == pUnkSite ) // 释放 band 的时?{ // 如果加蝲的时候,保存了一些接? // 那么现在Q释攑֮ } else // 加蝲 band 的时?{ m_hwndParent = NULL; // 装蝲 band 的父H口(是带有标题的那个框架窗? // q个H口的句柄,是调?IUnknown::QueryInterface() 得到 IOleWindow // 然后调用 IOleWindow::GetWindow() 而获得的? CComQIPtr< IOleWindow, &IID_IOleWindow > spOleWindow(pUnkSite); if( spOleWindow ) spOleWindow->GetWindow(&m_hwndParent); if( !m_hwndParent ) return E_FAIL; // 现在Q正好是建立子窗口的时机? // 注意Q子H口建立的时候,不要使用 WS_VISIBLE 属? ... ... // 在例子程序中Q用 CAxWindow 实现了一个能包容ActiveX的容器窗?垂直览器栏) // 在例子程序中Q用 WIN API 函数 CreateWindow 实现了标准窗?水^览器栏、工h) // 在例子程序中Q用 CWindowImpl 实现了一个包容窗?桌面工具? /*********************************************************/ 以下部分Q根?band 对象Ҏ(gu)的功能,是可以选择实现? **********************************************************/ // 如果子窗口实C用户输入Q那么必d?IInputObject 接口Q? // 而该接口是被 IE ?IInputObjectSite 调用的,因此在你的对? // 中,应该保存 IInputObjectSite 的接口指针? // 在类的头文g中,定义Q? // CComQIPtr< IInputObjectSite, &IID_IInputObjectSite > m_spSite; m_spSite = pUnkSite; // 保存 IInputObjectSite 指针 if( !m_spSite ) return E_FAIL; // 你需要控?IE 的主框架吗? // 那么在类的头文g中,定义Q? // CComQIPtr< IWebBrowser2, &IID_IWebBrowser2 > m_spFrameWB; // 然后Q先取得 IServiceProvider,再取?IWebBrowser2 CComQIPtr < IServiceProvider, &IID_IServiceProvider> spSP(pUnkSite); if( !spSP ) return E_FAIL; spSP->QueryService( SID_SWebBrowserApp, &m_spFrameWB ); if( !m_spFrameWB) return E_FAIL; // 如果你取得了 IE L架的 IWebBrowser2 指针 // 那么Q当它发生了什么事情,你难道不想知道吗Q? // 定义QCComPtr m_spCP; CComQIPtr< IConnectionPointContainer, &IID_IConnectionPointContainer> spCPC( m_spFrameWB ); if( spCPC ) { spCPC->FindConnectionPoint( DIID_DWebBrowserEvents2, &m_spCP ); if( m_spCP ) { m_spCP->Advise( reinterpret_cast< IDispatch * >( this ), &m_dwCookie ); } } // 咳~~~ 不说了,看源码去吧。这里能q的事情太多?.. ... } return S_OK;}IDeskBand 是一个特D的 band 对象接口Q有一个方法函敎ͼ(x)GetBarInfo()Q?br>IDockingWindow ?IDeskBank 的基c,?个方法函敎ͼ(x)ShowDW()、CloseDW()、ResizeBorderDW()Q?br>IOleWindow 又是 IDockingWindow 的基c,?个方法函敎ͼ(x)GetWindow()、ContextSensitiveHelp()Q?
class ATL_NO_VTABLE Cxxx : ...... public IDeskBand, ......{......BEGIN_COM_MAP(Cxxx) ...... COM_INTERFACE_ENTRY_IID(IID_IDeskBand, IDeskBand) ......END_COM_MAP()// IOleWindowSTDMETHODIMP Cxxx::GetWindow(HWND * phwnd){ // 取得 band 对象的窗口句?// m_hWnd 是徏立窗口时候保存的 *phwnd = m_hWnd; return S_OK;}STDMETHODIMP Cxxx::ContextSensitiveHelp(BOOL fEnterMode){ // 上下文帮助,参?IContextMenu 接口 return E_NOTIMPL;}// IDockingWindowSTDMETHODIMP CVerticalBar::ShowDW(BOOL bShow){ // 昄或隐?band H口 if( m_hWnd ) ::ShowWindow( m_hWnd, bShow ? SW_SHOW : SW_HIDE); return S_OK;}STDMETHODIMP CVerticalBar::CloseDW(DWORD dwReserved){ // 销?band H口 if( ::IsWindow( m_hWnd ) ) ::DestroyWindow( m_hWnd ); m_hWnd = NULL; return S_OK;}STDMETHODIMP CVerticalBar::ResizeBorderDW(LPCRECT prcBorder, IUnknown* punkToolbarSite, BOOL fReserved){ // 当框架窗口的Ҏ(gu)大小改变?return E_NOTIMPL;}// IDeskBandSTDMETHODIMP CVerticalBar::GetBandInfo(DWORD dwBandID, DWORD dwViewMode, DESKBANDINFO* pdbi){ // 取得 band 的基本信息,你需要填?pdbi 参数作ؓ(f)q回 if( NULL == pdbi ) return E_INVALIDARG; // 如果来需要调?IOleCommandTarget::Exec() 则需要保存这2个参?m_dwBandID = dwBandID; m_dwViewMode = dwViewMode; if(pdbi->dwMask & DBIM_MINSIZE) { // 最尺? pdbi->ptMinSize.x = 10; pdbi->ptMinSize.y = 10; } if(pdbi->dwMask & DBIM_MAXSIZE) { // 最大尺?(-1 表示 4G) pdbi->ptMaxSize.x = -1; pdbi->ptMaxSize.y = -1; } if(pdbi->dwMask & DBIM_INTEGRAL) { pdbi->ptIntegral.x = 1; pdbi->ptIntegral.y = 1; } if(pdbi->dwMask & DBIM_ACTUAL) { pdbi->ptActual.x = 0; pdbi->ptActual.y = 0; } if(pdbi->dwMask & DBIM_TITLE) { // H口标题 wcscpy(pdbi->wszTitle,L"H口标题"); } if(pdbi->dwMask & DBIM_MODEFLAGS) { pdbi->dwModeFlags = DBIMF_VARIABLEHEIGHT; } if(pdbi->dwMask & DBIM_BKCOLOR) { // 如果使用默认的背景色Q则U除该标? pdbi->dwMask &= ~DBIM_BKCOLOR; } return S_OK;}3.3 选择实现?COM 接口
STDMETHODIMP CExplorerBar::UIActivateIO(BOOL fActivate, LPMSG pMsg){ if(fActivate) SetFocus(m_hWnd); return S_OK;}STDMETHODIMP CExplorerBar::HasFocusIO(void){ if(m_bFocus) return S_OK; return S_FALSE;}STDMETHODIMP CExplorerBar::TranslateAcceleratorIO(LPMSG pMsg){ return S_FALSE;}Band 对象能够通过包容器的 IOleCommandTarget::Exec() 调用执行命o(h)。?IOleCommandTarget 接口指针Q则可以通过调用包容器的 IInputOjbectSite::QueryInterfaceQIID_IOleCommandTarget,...Q?函数得到。CGID_DeskBand 是命令组Q当一?band 对象?GetBandInfo 被调用的时候,包容器通过 dwBandID 参数指定一?ID l?band 对象Q对象要保存住这个IDQ以便调?IOleCommandTarget::Exec()的时候用。ID 的命令有Q?
?/font> | 描述 |
---|---|
pUnk | band 对象?IUnknown 指针Q其它的桌面 bands 被隐藏 |
0 | 隐藏所有的桌面 bands |
1 | 昄所有的桌面 bands |
3.4 Band 对象注册
Band 对象必须注册Z?OLE q程内的服务器,q且支持 apartment U程公寓。注册表中默认键的值是表示菜单的文字。对于浏览器栏,它加?IE 菜单?#8220;查看\览器栏”中;对于工具?band Q它加到 IE 菜单?#8220;查看\工具?#8221;中;对于桌面 bandQ?它加到系lQ务栏的快捯单中。在菜单资源中,可以使用“&”指明加速键?br>
通常Q一个基本的 band 对象的注册表目是:(x)
HKEY_CLASSES_ROOT
CLSID
{你的 band 对象?CLSID}
(Default) = 菜单的文?
InProcServer32
(Default) = DLL 的全路径文g?
ThreadingModel= Apartment
工具?bands q必L它们?CLSID 注册?IE 的注册表中?br>
?HKEY_LOCAL_MACHINE\Software\Microsoft\Internet Explorer\Toolbar 下给?CLSID 作ؓ(f)键名Q而其键值是被忽略的?br>
HKEY_LOCAL_MACHINE
Software
Microsoft
Internet Explorer
Toolbar
{你的 band 对象?CLSID}
q有几个可选的注册表项?例子E序q不是这样实现的)。比如,你想让浏览器栏显C?HTML 的话Q必要如下讄注册表:(x)
HKEY_CLASSES_ROOT
CLSID
{你的 Band 对象?CLSID}
Instance
CLSID
(Default) = {4D5C8C2A-D075-11D0-B416-00C04FB90376}
同时Q如果要指定一个本地的 HTML 文gQ那么要如下讄Q?
HKEY_CLASSES_ROOT
CLSID
{你的 Band 对象?CLSID}
Instance
InitPropertyBag
Url
另外Q还可以指定览器栏的宽和高Q当Ӟ它是依赖于这个栏是纵向还是横向的。其实这个项目无所谓,因ؓ(f)当用戯整了览器栏的大后Q会(x)自动保存在注册表中的?br>
HKEY_CURRENT_USER
Software
Microsoft
Internet Explorer
Explorer Bars
{你的 Band 对象?CLSID}
BarSize
BarSize 键的cd必须?REG_BINARY cdQ它?个字节。左起前4个字节,是用16q制表示的像素宽度或高度Q后4个字节保留,你应该设|ؓ(f)0。下面是一个可以在览器栏上显C?HTML 文g的全部注册表目的例子,默认宽度?91Q?x123Q个像素点:(x)
HKEY_CLASSES_ROOT
CLSID
{你的 Band 对象?CLSID}
(Default) = 菜单文字
InProcServer32
(Default) = DLL 的全路径文g?
ThreadingModel= Apartment
Instance
CLSID
(Default) = {4D5C8C2A-D075-11D0-B416-00C04FB90376}
InitPropertyBag
Url= 你的 HTML 文g?br>
HKEY_CURRENT_USER
Software
Microsoft
Internet Explorer
Explorer Bars
{你的 Band 对象?CLSID}
BarSize= 23 01 00 00 00 00 00 00
对于注册表的讄Q用 ATL 实现其实是异常简单的。打开工程?xxx.rgs 文gQƈ手工~辑一下就可以了?下面q个文g源码Q是例子E序?IE 工具栏的注册表样式,HKLM 是需要手工添加的Q因为它不用组件类型方式注册。而对于其它类型的 band 对象只要在类声明中添加:(x)
BEGIN_CATEGORY_MAP(Cxxx) // 向注册表中注?COM cd IMPLEMENTED_CATEGORY(CATID_InfoBand) // 垂直样式的浏览器栏END_CATEGORY_MAP()IE 工具栏类?band 对象?#8220;.rgs”文g
HKCR // q个目?ATL 帮你生成的,你只要手工修?#8220;菜单上的文字”可以了{ Bands.ToolBar.1 = s ''ToolBar Class'' { CLSID = s ''{ 你的 CLSID }'' } Bands.ToolBar = s ''ToolBar Class'' { CLSID = s ''{ 你的 CLSID }'' CurVer = s ''Bands.ToolBar.1'' } NoRemove CLSID { ForceRemove { 你的 CLSID } = s ''用在菜单上的文字(&T)'' { ProgID = s ''Bands.ToolBar.1'' VersionIndependentProgID = s ''Bands.ToolBar'' ForceRemove ''Programmable'' InprocServer32 = s ''%MODULE%'' { val ThreadingModel = s ''Apartment'' } ''TypeLib'' = s ''{xxxx-xxxx-xxxxxxxxxxxxxxx}'' } }}HKLM // q个目是手工添加的IE工具栏所Ҏ(gu)的{ Software { Microsoft { ''Internet Explorer'' { NoRemove Toolbar { ForceRemove val { 你的 CLSID } = s ''随便l个说明性文字串'' } } } }}四?ATL 实现
IDocHostUIHandler::GetDropTarget Method——Called by MSHTML when it is used as a drop target. This method enables the host to supply an alternative IDropTarget interface.
STDMETHODIMP CHtmlControlSite::XDocHostUIHandler::GetDropTarget(LPDROPTARGET pDropTarget, LPDROPTARGET* ppDropTarget){METHOD_PROLOGUE_EX_(CHtmlControlSite, DocHostUIHandler)*ppDropTarget = g_pDropTarget;//自定义的实现告知MSHTML引擎return S_OK;}
IHTMLDataTransfer MembersclearData——Removes one or more data formats from the clipboard through dataTransfer or clipboardData object.dropEffect——Sets or retrieves the type of drag-and-drop operation and the type of cursor to display.effectAllowed——Sets or retrieves, on the source element, which data transfer operations are allowed for the object.getData——Retrieves the data in the specified format from the clipboard through the dataTransfer or clipboardData objects.setData——Assigns data in a specified format to the dataTransfer or clipboardData object.
HRESULT CHtmlDocument2::OnInvoke(DISPID dispidMember, REFIID riid, LCID lcid, WORD wFlags,DISPPARAMS * pdispparams, VARIANT * pvarResult,EXCEPINFO * pexcepinfo,UINT * puArgErr){......//如果只是要设|鼠标拖拽效果的话,q个事g可以不处?/font>case DISPID_HTMLELEMENTEVENTS_ONDRAGSTART :{OnDragStart();break ;}//重点在这?/font>case DISPID_HTMLELEMENTEVENTS_ONDRAGOVER :{OnDragOver();break ;}case DISPID_HTMLELEMENTEVENTS_ONDROP :{OnDrop();break ;}......}void CHtmlDocument2::OnDragOver( void ){SetDragEffect(); //讄鼠标拖拽效果}void CHtmlDocument2::SetDragEffect( void ){CComQIPtr<IHTMLWindow2> pWindow;CComQIPtr<IHTMLEventObj> pEventObj;CComQIPtr<IHTMLEventObj2> pEventObj2;CComQIPtr<IHTMLElement> pElement;HRESULT hr = m_spHtmlObj->get_parentWindow( &pWindow );hr = pWindow->get_event( &pEventObj );//ondragover发生时IE的默认行为是“没有鼠标拖拽效果”?/font>//IHTMLEventObj的返回D为false卛_取消该事件的默认行ؓ(f)Q所以执行完下面q句话,拖拽效果出C?/font>AllowDisplayDragCursor(pEventObj, FALSE);CComBSTR bstrTagName;pEventObj->get_srcElement(&pElement); //获得当前HTML ElementpElement->get_tagName(&bstrTagName);if ( IsEditArea(bstrTagName) ) //Ҏ(gu)Tag Name判断是否鼠标位于输入框,以便讄焦点使得光标随鼠标移?/font>{CComQIPtr<IHTMLElement2> pElement2;if ( SUCCEEDED(pElement->QueryInterface(IID_IHTMLElement2, (void **) &pElement2 ))&& pElement2 ){pElement2->focus();}//默认情况下,当拖拽文档到输入框时Q鼠标会(x)变成拖拽的光标,所以这里用IE的默认行为?/font>AllowDisplayDragCursor(pEventObj, TRUE);}}BOOL CHtmlDocument2::IsEditArea(CComBSTR bstrTagName){return bstrTagName == "INPUT" || bstrTagName == "TEXTAREA";}void CHtmlDocument2::AllowDisplayDragCursor(CComQIPtr<IHTMLEventObj> pEventObj, BOOL bAllow){VARIANT v;v.vt = VT_BOOL;v.boolVal = !bAllow ? VARIANT_FALSE : VARIANT_TRUE;pEventObj->put_returnValue(v);}void CHtmlDocument2::OnDrop( void ){CComQIPtr<IHTMLWindow2> pWindow;CComQIPtr<IHTMLEventObj> pEventObj;CComQIPtr<IHTMLEventObj2> pEventObj2;CComQIPtr<IHTMLElement> pElement;CComQIPtr<IHTMLDataTransfer> pdt; //此处演示如何使用IHTMLDataTransferHRESULT hr = m_spHtmlObj->get_parentWindow( &pWindow );hr = pWindow->get_event( &pEventObj );hr = pEventObj->QueryInterface(IID_IHTMLEventObj2, (void **) &pEventObj2 );hr = pEventObj2->get_dataTransfer(&pdt);CComBSTR bstrFormat = "URL"; //首先试获取URLVARIANT Data;hr = pdt->getData(bstrFormat, &Data);if ( Data.vt != VT_NULL ){ //获取成功Q拖攄对象是UrlDoOpenUrl(CString(Data.bstrVal));}else{ //否则试获取选中的文?/font>bstrFormat = "Text";hr = pdt->getData(bstrFormat, &Data);if ( Data.vt != VT_NULL ){ //获取成功Q拖攄内容是文?/font>CComBSTR bstrTagName;pEventObj->get_srcElement(&pElement);pElement->get_tagName(&bstrTagName);if ( IsEditArea(bstrTagName) ){//Drop target是输入框Q不做Q何操作,由IEq行默认处理return ;}else{ //否则我们自己处理文本Q或保存Q或是否链接后打开Q等{?/font>DoProcessText(CString(Data.bstrVal));//Process the text}}else{ //既不是链接,也不是文本,可认为是来自外部Q如Windows ShellQ的文g拖放DoOnDropFiles(pdt);}}}//演示如何从IHTMLDataTransfer得到IDataObjectvoid CHtmlDocument2::DoOnDropFiles(CComQIPtr<IHTMLDataTransfer> pDataTransfer)
{CComQIPtr<IServiceProvider> psp;CComQIPtr<IDataObject> pdo;if ( FAILED(pDataTransfer->QueryInterface(IID_IServiceProvider, (void **) &psp)) ){return ;}if ( FAILED(psp->QueryService(IID_IDataObject, IID_IDataObject, (void **) &pdo)) ){return ;}COleDataObject DataObject;DataObject.Attach(pdo);......}
HRESULT GetDropTarget( IDropTarget *pDropTarget, IDropTarget **ppDropTarget);
参数说明
pDropTarget
[in] Pointer to an IDropTarget interface for the current drop target object supplied by MSHTML.
ppDropTarget
[out] Address of a pointer variable that receives an IDropTarget interface pointer for the alternative drop target object supplied by the host.
惛_了吗Q解决问题的关键在于第一个参数pDropTarget。相信很多浏览器在处理的时候都忽略掉了W一个参数而只是将自己的实现通过W二个参数告知MSHTMLQ因而丢׃IE~省的行为。既然如此,缺省的IDropTarget接口的指针保存下来,在适当的时候调用,不就能够保留IE的原始拖放行Z吗?
//构造函敎ͼ传入参数x从GetDropTarget得到的那个pDropTargetQ它是MSHTML的缺省实?/font>CBrowserDropTarget::CBrowserDropTarget(IDropTarget *pOrginalDropTarget): m_bDragTextToInputBox(FALSE)//q个布尔变量用来判断是否正在向InputBox拖拽文字, m_pOrginalDropTarget(pOrginalDropTarget)//m_pOrginalDropTarget用来保存MSHTML的缺省实?/font>{}STDMETHODIMP CBrowserDropTarget::DragEnter(/* [unique][in] */IDataObject __RPC_FAR *pDataObj,/* [in] */ DWORD grfKeyState,/* [in] */ POINTL pt,/* [out][in] */ DWORD __RPC_FAR *pdwEffect){//调用~省的行?/font>return m_pOrginalDropTarget->DragEnter(pDataObj, grfKeyState, pt, pdwEffect);}STDMETHODIMP CBrowserDropTarget::DragOver(/* [in] */ DWORD grfKeyState,/* [in] */ POINTL pt,/* [out][in] */ DWORD __RPC_FAR *pdwEffect){//在网内拖拽文字时这个值是DROPEFFECT_COPYQ拖拽的文字不属于输入框中)//或DROPEFFECT_COPY | DROPEFFECT_MOVEQ拖拽的文字是输入框中的文字Q?/font>DWORD dwTempEffect = *pdwEffect;//接下来调用IE的缺省行?/font>HRESULT hr = m_pOrginalDropTarget->DragOver(grfKeyState, pt, pdwEffect);//判断是否是往输入框拖拽文?/font>m_bDragTextToInputBox = IsDragTextToInputBox(dwOldEffect, *pdwEffect);if ( !m_bDragTextToInputBox ){//不是往输入框拖拽文字,则用原始的拖拽效果。否则和IE的缺省效果一样——也是没有效果*pdwEffect = dwTempEffect;}return S_OK;}//Ҏ(gu)调用~省行ؓ(f)前后的Effect值判断是否是往输入框拖拽文?/font>BOOL CBrowserDropTarget::IsDragTextToInputBox(DWORD dwOldEffect, DWORD dwNewEffect){//如果是把非输入框中文字往输入框拖动,则dwOldEffect与dwNewEffect相等Q都是DROPEFFECT_COPYBOOL bTextSelectionToInputBox = ( dwOldEffect == DROPEFFECT_COPY )&& ( dwOldEffect == dwNewEffect );//如果是把文字从一个输入框拖到另一个输入框Q则dwOldEffect为DROPEFFECT_COPY | DROPEFFECT_MOVEQ?/font>//而dwNewEffect的值可能ؓ(f)DROPEFFECT_MOVEQ默认情况)Q也可能为DROPEFFECT_COPYQ按下Ctrl键时Q?/font>BOOL bInputBoxToInputBox = ( dwOldEffect == (DROPEFFECT_COPY | DROPEFFECT_MOVE) )&& ( dwNewEffect == DROPEFFECT_MOVE || dwNewEffect == DROPEFFECT_COPY );//来自Microsoft Word的拖拽特D一些,dwOldEffect是所有效果的l合?/font>BOOL bMSWordToInputBox =( dwOldEffect == (DROPEFFECT_COPY | DROPEFFECT_MOVE | DROPEFFECT_LINK) )&& ( dwNewEffect == DROPEFFECT_MOVE || dwNewEffect == DROPEFFECT_COPY );//来自Edit Plus的拖拽过也特D一些,dwOldEffect是个负数Q怀疑是Edit Plus的拖拽实现有问题Q?/font>BOOL bEditPlusToInputBox = ( dwOldEffect < 0 )&& ( dwNewEffect == DROPEFFECT_MOVE || dwNewEffect == DROPEFFECT_COPY );//也许q有些例外,可再d......return bTextSelectionToInputBox || bInputBoxToInputBox || bMSWordToInputBox || bEditPlusToInputBox;}STDMETHODIMP CBrowserDropTarget::DragLeave(){//调用~省的行?/font>return m_pOrginalDropTarget->DragLeave();}STDMETHODIMP CBrowserDropTarget::Drop(/* [unique][in] */ IDataObject __RPC_FAR *pDataObj,/* [in] */ DWORD grfKeyState,/* [in] */ POINTL pt,/* [out][in] */ DWORD __RPC_FAR *pdwEffect){if ( m_bDragTextToInputBox ){//是文字拖放,调用IE的缺省行?/font>return m_pOrginalDropTarget->Drop(pDataObj, grfKeyState, pt, pdwEffect);}//否则是拖N接、图片、文件等Q按常规的IDataObject处理方式......return S_OK;}
Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=677425
#include "HtmlObj.h"class CHtmlAnchorElement : public CHtmlObj<IHTMLAnchorElement,&DIID_HTMLAnchorEvents>{public:CHtmlAnchorElement(CHtmlDocument2* pParentDoc2);virtual ~CHtmlAnchorElement();virtual HRESULT OnInvoke(DISPID dispidMember,REFIID riid,LCID lcid, WORD wFlags,DISPPARAMS * pdispparams, VARIANT * pvarResult,EXCEPINFO * pexcepinfo,UINT * puArgErr);};
HRESULT CHtmlAnchorElement::OnInvoke(DISPID dispidMember,REFIID riid, LCID lcid, WORD wFlags,DISPPARAMS * pdispparams, VARIANT * pvarResult,EXCEPINFO * pexcepinfo,UINT * puArgErr){HRESULT hr = E_NOTIMPL;switch(dispidMember){case DISPID_HTMLELEMENTEVENTS_ONMOUSEOVER :{//当鼠标经q链接时Q我们在q里获得通知hr = S_OK;// TODO: add code to handle on mouse over eventsbreak;}case DISPID_HTMLELEMENTEVENTS_ONMOUSEOUT :{//当鼠标从链接上移开Ӟ我们在这里获得通知Q其它的Dispatch ID可根据需要添?/font>hr = S_OK;// TODO: add code to handle on mouse out eventsbreak;}default:{break;}}return hr;}
template<class THtmlElement> class CHtmlElements{typedef CMap<LPDISPATCH, LPDISPATCH, THtmlElement*, THtmlElement*> CMapDispToHtmlElement;CMapDispToHtmlElement m_htmlElements;BOOL IsSiteConnected( LPDISPATCH pDisp ){THtmlElement *pElement;return m_htmlElements.Lookup( pDisp, pElement );}public :CHtmlElements( void ){}~CHtmlElements( void ){}public :void SetSite( LPDISPATCH pDisp ){if ( IsSiteConnected( pDisp ) ) //查以避免多余的Sink{return ;}THtmlElement *pElement = new THtmlElement; //通过模板cd创徏相应的类的实例进行连?/font>pElement->SetSite( pDisp );m_htmlElements.SetAt( pDisp, pElement );}//在合适的地方调用Clear释放所理的内?/font>
void Clear(void)
{
POSITION pos = m_htmlElements.GetStartPosition();
THtmlElement *pElement = NULL;
LPDISPATCH pDisp = NULL;
while (pos != NULL)
{
m_htmlElements.GetNextAssoc( pos, pDisp, pElement );
m_htmlElements.RemoveKey( pDisp );
delete pElement;
}
}
};
typedef CHtmlElements<CHtmlDocument2> CHtmlDocuments;typedef CHtmlElements<CHtmlAnchorElement> CHtmlAnchors;class CMyView : public CHtmlView{private :CHtmlDocuments m_htmlDocs;CHtmlAnchors m_htmlAnchors;}
void CMyView ::OnDocumentComplete(LPDISPATCH pDisp, LPCTSTR lpszURL){m_htmlDocs.SetSite(pDisp);}
Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=621961
1、概q?/font>
Internet Explorer强大而方便的可编E能力和可扩展能力ؓ(f)其抢占浏览器市场可谓是立下了汗马功劳。可~程主要体现两方面:(x)
可扩展能力则体现在几个方面:(x)
随着IE的发展,各种cd的扩展遍地开花,其中最为引人注目的Q当属地址栏扩展和工具条扩展(几乎成了兵家必争之地Q。本文讨论的主题Q正是工h的扩展?/font>
2、问题的提出
两个原因促成了Google Toolbar的流行,一是广告窗口的泛滥、二是Google Search。Google“?#8221;Q实则一炚w不简单,没有搜烦引擎的强力支持,Toolbar的用途就大受限制Q地抓住了这两点Q迅速占领了市场?/font>
插g的一大好处在于可以不修改ȝ序,只需换一个样子差不多但功能更强的东西可以得整个应用程序功能增强,所以IE不升U大家也觉得Google Toobar来好用。于是利用WebBroser Control~写览器的开发h员就惻I如果能像IE一h持上q这些扩展,不就能把Google Toolbar拿过来用了吗Q其他的事交lGoogledp了。这是我们要讨论的问题Q?#8220;如何在自q览器中嵌入Google Toolbar”?/font>
3、分?/font>
微Yq未在MSDN中说明如何将Google Toolbarq类IE的工h插g嵌入自己的应用程序,但其ZCOM的设计方法实际上l予了我们这个能力(创徏嵌入式的工具条的Ҏ(gu)q不是本文的重点Q此处略去,有兴的朋友可以参考MSDNQ。我们知道,除了IUnknown接口外,Bands和BarsQ以下简UBand对象Q还需要实C个接口:(x)IObjectWithSiteQIPersistStream和IDeskBand。当用户选择工具条或面板Ӟ其容器(如IE的外xӞ׃(x)调用Band对象的IObjectWithSite::SetSiteҎ(gu)Q该Ҏ(gu)仅需要一个IUnknowncd的指针)Q将自己实现的IUnknown指针传递给Band对象。这是整个插g开始真正激zȝ入口Q也是我们的着手点?/font>
MSDN中说刎ͼ一般来_(d)Band对象对于SetSiteҎ(gu)的实现需要完成以下几件事Q?/font>
昄Q就我们要讨论的问题而言Q只需换个角度Q编写IE外壳的的角度Q来考虑卛_。首先,我们需要一个IUnknown接口Q即Band对象所需的SiteQ,其次需要一个IInputObjectSite接口Q用以和Band对象的IInputObject接口交互Q处理输入焦点{Uȝ情况。接下来可以通过Band对象的IDeskBand接口来显C、隐藏Band对象了(注意IDeskBand接口z自IDockingWindow接口,后者又z自IOleWindow接口Q?/font>
4、实?/font>
实现分ؓ(f)两个部分Q其一是一个简单的c,用以模拟IE外壳Q我取名为CIESimulator。其二是一个管理IE扩展的类CIEBandPlugInManagerQ用以管理Band对象的方斚w面?/font>
class CIESimulator : public IInputObjectSite
{
private:
IWebBrowser2 *m_pwb; //保存WebBrowser Control的接口指?/font>
public:
CIESimulator(void){};
~CIESimulator(void){};
void SetIWebBrowser2(IWebBrowser2* pwb);
//IUnknown methods
STDMETHODIMP QueryInterface(REFIID, void **);
STDMETHODIMP_(ULONG) AddRef(void);
STDMETHODIMP_(ULONG) Release(void);
//IInputObjectSite specific methods
STDMETHOD(OnFocusChangeIS)(THIS_ IUnknown* punkObj, BOOL fSetFocus);
};
//IUnknown methods
STDMETHODIMP CIESimulator::QueryInterface( REFIID riid, void **ppv )
{
if ( riid == IID_IInputObjectSite ) //q个接口需要自己处?/font>
{
*ppv = static_cast<IInputObjectSite*>(this);
}
else if ( m_pwb ) //其它的交lWebBrowser Controld?/font>
{
m_pwb->QueryInterface( riid, ppv );
}
return S_OK;
}
//IInputObjectSite specific methods
STDMETHODIMP CIESimulator::OnFocusChangeIS(IUnknown* punkObj, BOOL fSetFocus)
{
return S_OK; //此处我们单地q回
}
void CIESimulator::SetIWebBrowser2(IWebBrowser2* pwb)
{
m_pwb = pwb;
}
注意q里我们q没有实现IOleWindow接口来向B(ti)and对象传递父H口对象Q窗口的宿主可以更改Q所以Band对象创徏的窗口的父窗口我们ƈ不关心,Band对象查询IOleWindow接口的动作实际上是向WebBrowser Control查询Q,而是在稍后的CIEBandPlugInManagercM通过调用IDeskBand的GetWindowҎ(gu)获得Band对象的窗口句柄,再手动将其嵌入我们指定的H口中?/font>
首先我们定义一个结构用以保存Band的信息:(x)
enum eBANDTYPE
{
btVertical = 0,
btHorizontal = 1
};
enum eBANDSTATE
{
bsUnInitialized = -1,
bsVisible = 0,
bsInVisible = 1,
bsUnLoaded = 2
};
typedef struct tagIEBANDINFO
{
char
szCLSID[39];
char
szName[MAX_PATH];
IUnknown
*puk;
HWND
hBand;
UINT
uMinHeight;
UINT
uBandID;
eBANDTYPE
eBandType;
eBANDSTATE
eBandState;
} IEBANDINFO, *LPIEBANDINFO;
再用一个函数来获取所有Band的信息(以下代码为示例,具体实现是可从注册表把所有Band的信息一一dQ?/font>
void CIEBandPlugInManager::GetAllBandCLSID(void)
{
LPIEBANDINFO pIEBandInfo;
pIEBandInfo = new IEBANDINFO();
strcpy( pIEBandInfo->szCLSID, "{2318C2B1-4965-11d4-9B18-009027A5CD4F}\0"); //Google Toolbar的CLSID
strcpy( pIEBandInfo->szName, GetDisplayName(pIEBandInfo->szCLSID) );
pIEBandInfo->uMinHeight = 22;
pIEBandInfo->uBandID = m_BandCtrlID++;
pIEBandInfo->eBandType = btHorizontal;
pIEBandInfo->eBandState = bsUnInitialized;
m_oaBand.Add( (CObject*)pIEBandInfo );//m_oaBand是一个CObArray
?/font>
//Ҏ(gu)CLSID从注册表获取Band的名U?/font>
CString CIEBandPlugInManager::GetDisplayName(CString strCLSID)
{
TCHAR
sz[MAX_PATH];
HKEY
hKey;
DWORD
dwSize;
strCLSID = "CLSID\\" + strCLSID;
if(RegOpenKey(HKEY_CLASSES_ROOT, strCLSID, &hKey) != ERROR_SUCCESS)
{
return _T("");
}
RegQueryValueEx(hKey, NULL, NULL, NULL, (LPBYTE)sz, &dwSize);
RegCloseKey(hKey);
return sz;
}
//通过Band的CLSIDȀzBand
bool CIEBandPlugInManager::ActivateBand( CString strCLSID )
{
LPIEBANDINFO pIEBandInfo = GetBandInfo( strCLSID ); //从m_oaBand中找到符合条件的Band的信?/font>
if ( !pIEBandInfo )
{
return false;
}
WCHAR wsz[MAX_PATH];
::MultiByteToWideChar(CP_ACP, 0, strCLSID, -1, wsz, MAX_PATH);
CLSID
clsid;
HRESULT hr2 = CLSIDFromString( wsz, &clsid);
if ( hr2 != NOERROR )
return false;
HRESULT hr = ::CoCreateInstance(clsid, NULL, LSCTX_INPROC_SERVER, IID_IUnknown, (void**)&pIEBandInfo->puk); //创徏Band对象的实?/font>
IUnknown* puk = pIEBandInfo->puk;
if (FAILED(hr))
return false;
DoQueryBandInfo( pIEBandInfo ); //查询Band对象实例的信?/font>
switch( pIEBandInfo->eBandType )
{
case btVertical:
break;
//我们不处理Vertical的面?/font>
case btHorizontal:
{
g_pMainFrame->m_wndReBar.AddBar2( pIEBandInfo->hBand, pIEBandInfo->uBandID, pIEBandInfo->uMinHeight, 0, 0); //Band嵌入ȝ口的ReBar?/font>
REBARBANDINFO rbbi;
rbbi.cbSize = sizeof(rbbi);
rbbi.fMask = RBBIM_CHILDSIZE | RBBIM_IDEALSIZE | RBBIM_SIZE;
rbbi.cxMinChild = 0;
rbbi.cyMinChild = pIEBandInfo->uMinHeight;
rbbi.cx = rbbi.cxIdeal = 250;
UINT nCount = g_pMainFrame->m_wndReBar.GetReBarCtrl().GetBandCount();
g_pMainFrame->m_wndReBar.GetReBarCtrl().SetBandInfo(nCount-1, &rbbi);
break;
}
default:
break;
}
pIEBandInfo->eBandState = bsVisible;
return true;
}
//查询Band对象实例的信?/font>
void CIEBandPlugInManager::DoQueryBandInfo(LPIEBANDINFO pIEBandInfo)
{
IObjectWithSite *pOWS;
//查询IObjectWithSite接口
HRESULT hr = pIEBandInfo->puk->QueryInterface(IID_IObjectWithSite, (void**)&pOWS);
if (SUCCEEDED(hr))
{ //讄Site
pOWS->SetSite( (IUnknown *)&m_IESimulator ); //m_IESimulator是CIESimulator的一个实例对象,对Band对象而言Q它?yu)像IE
}
IDeskBand *pdb;
hr = pIEBandInfo->puk->QueryInterface(IID_IDeskBand, (void**)&pdb);
if (SUCCEEDED(hr))
{ //查询得到Band对象H口的句柄,E候通过该句柄将Band对象的窗口嵌入我们指定的H口
pdb->GetWindow(&pIEBandInfo->hBand);
}
ShowBand(pIEBandInfo, TRUE);//昄Band
}
bool CIEBandPlugInManager::ShowBand(LPIEBANDINFO pIEBandInfo, bool bShow)
{
IDockingWindow *pdw;
HRESULT hr = pIEBandInfo->puk->QueryInterface(IID_IDockingWindow, (void**)&pdw);
if (SUCCEEDED(hr))
{
pdw->ShowDW(bShow);
}
else
{
return false;
}
return true;
}
下面是我试的一个截图,Google的搜索、广告窗口拦截均可正常工作?/font>
5、ȝ
上述的原理看来很单,但具体实现的时候仍焉要作较多的测试和考虑QBand对象的管理和~存、接口的AddRef和Release{。时间有限,代码也很乱,不过只要原理交待清楚Q相信会(x)Ҏ(gu)兴趣的朋友有所帮助?/font>
6、参考资?/font>
MSDN:?font face="Courier New">Creating Custom Explorer Bars, Tool Bands, and Desk Bands?/font>
引用地址Q?a >Internet Explorer~程qͼ?ji))在自q览器中嵌入Google工具?/a>?/p>
Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=550698
1、概q?/p>
Internet Explorer的浏览历史菜单在4.0版本开始出玎ͼ但直?.5之前Q微软都未公布用于访问浏览历史的COM接口Q如今已是IE6.0大行光的年代,用于讉K览历史的接口也早已公布多时Q本文的目的则是试图抛砖引玉Q简单介l用于访问浏览历史的Travel Log接口Qƈ用一个小的cCIETravelLog来实现对Travel Log的封装?/p>
2、IOmHistory接口
在早些时候的MSDN中,我们能够查阅到关于浏览历史的接口仅有IOmHistoryQ而该接口实际上对应的是浏览器中可以通过脚本讉K?#8220;history”对象。对?#8220;history”对象QMSDN中是q样说的Q?/p>
For security reasons, the history object does not expose the actual URLs in the browser history. It does allow navigation through the browser history by exposing the back, forward, and go methods. A particular document in the browser history can be identified as an index relative to the current page. For example, specifying -1 as a parameter for the go method is the equivalent of clicking the Back button.
This object is available in script as of Microsoft Internet Explorer 3.0.
即ؓ(f)了安全的原因QIOmHistory接口仅提供了有限的几个方法来完成在浏览器中前q、后退{操作,q没有提供访问历史列表Url的能力。这也难怪,该接口在IE 3.0时代已经存在Q而当时IEq不成熟Q可~程能力也不甚强大。一直到IE 4.0通过与Windows 98捆绑销售一l天下之后,相关的文档才逐渐丰富Q多H口览器等ZInternet Explorer/WebBrowser Control的应用Y件也才铺天盖地开来。但在IE 5.5接口公布之前Q要完全模拟IE的Travel Log行ؓ(f)Qƈ不是一件容易的事。最Ҏ(gu)惛_的方法就是在BeforeNavigate、DocumentComplete{事件发生之时记?修改Urlq加以保存(我在早些时候也q样做过Q,但是效果不甚理想Q尤其是览包含Frame的网|Q处理更是麻烦。当Ӟ要完全模拟亦非难事,只不q开发h员都知道微Y公布接口是早晚的事,所以也没有大力气在模拟IE的Travel Log行ؓ(f)上?/p>
3、Travel Log?/p>
Internet Explorer 5.5推出以后QTravel Log接口也就开始出现在MSDN中,它是专门为OLE嵌入WebBrowser Control的应用程序设计的Q其目的?#8220;提高和加强用L(fng)讉K日志体验”Qimprove and enhance the user's travel log experienceQ。事实上Q稍后我?x)提刎ͼTravel Log接口正日益成为应用程序中的重要接口之一?/p>
微Y公布的Travel Log共包含三个接口:(x)ITravelLogStg, IEnumTravelLogEntry和ITravelLogEntry?/p>
Ҏ(gu)?nbsp; 用?/p>
EnumEntries 问日志项创徏枚D器(IEnumTravelLogEntry接口指针Q?/p>
GetRelativeEntry q回一个日志项
TravelTo 讉K一个日志项
Ҏ(gu)?nbsp; 用?/p>
Next 枚D下一个日志项Q返回ITravelLogEntry接口指针Q?/p>
Ҏ(gu)?nbsp; 用?/p>
GetTitle q回日志的Title
GetURL q回日志的Url
接口准备好了Q我们也很Ҏ(gu)得知它们之间的关p:(x)
也许不是太恰当,我对UML也不熟?zhn)Q借用一个伪UML序列图表C其关系如下Q?/p>
4、封装Travel Log
接下来,我们q一个简单的cL完成对Travel Log的封装。如下所C,tlogstg.h包含了Travel Log的相x口声明,该头文g可以在Platform SDK中找到?/p>
#include "tlogstg.h"
class CIETravelLog
{
private:
ITravelLogStg *m_pTravelLogStg;
IEnumTravelLogEntry *m_pEnumLogEntry;
ITravelLogEntry *m_pTravalLogEntry;
IWebBrowser2* m_pWebBrowser;
public:
CIETravelLog(void);
~CIETravelLog(void);
void SetWebBrowser(IWebBrowser2* pWebBrowser);
void BuildHistoryMenu(CMenu * pMenu, unsigned char nCount, bool bForward);
void TravelTo(int nPosition);
};
CIETravelLog::CIETravelLog(void)
: m_pTravelLogStg(NULL), m_pEnumLogEntry(NULL), m_pTravalLogEntry(NULL), m_pWebBrowser(NULL)
{
}
CIETravelLog::~CIETravelLog(void)
{
if ( m_pTravelLogStg != NULL )
{
m_pTravelLogStg->Release();
}
if ( m_pEnumLogEntry != NULL )
{
m_pEnumLogEntry->Release();
}
if ( m_pTravalLogEntry != NULL )
{
m_pTravalLogEntry->Release();
}
if ( m_pWebBrowser != NULL )
{
m_pWebBrowser->Release();
}
}
//浏览器的IWebBrowser2接口指针赋予CIETravelLog的实?/font>
void CIETravelLog::SetWebBrowser(IWebBrowser2* pWebBrowser)
{
if ( (m_pWebBrowser == pWebBrowser) || (m_pWebBrowser == NULL) )
{
return;
}
if ( m_pWebBrowser != NULL )
{
m_pWebBrowser->Release();
}
m_pWebBrowser = pWebBrowser;
IServiceProvider *pSP;
HRESULT hr = m_pWebBrowser->QueryInterface(IID_IServiceProvider, (LPVOID*)&pSP);
m_pWebBrowser->Release();
if (SUCCEEDED(hr))
{
hr = pSP->QueryService(SID_STravelLogCursor, IID_ITravelLogStg, (LPVOID*)&m_pTravelLogStg);
pSP->Release();
}
}
//创徏览历史菜单QbForward指明是前q还是后退菜单
void CIETravelLog::BuildHistoryMenu(CMenu * pMenu, unsigned char nCount, bool bForward)
{
if ( m_pTravelLogStg == NULL )
{
return;
}
TLENUMF eFlag = bForward ? TLEF_RELATIVE_FORE : TLEF_RELATIVE_BACK;
if ( FAILED(m_pTravelLogStg->EnumEntries( eFlag, &m_pEnumLogEntry ) ) )
{
return;
}
ULONG uFetched;
int i=0;
if ( m_pEnumLogEntry !=NULL )
{
while ( SUCCEEDED( m_pEnumLogEntry->Next( 1, &m_pTravalLogEntry, &uFetched ) ) &&
m_pTravalLogEntry && i<10 )//我们最多只需?0条历史菜单,可根据实际情况修?/font>
{
LPOLESTR pszTitle;
m_pTravalLogEntry->GetTitle( &pszTitle );
CString strTitle = pszTitle;
if ( bForward )
{
//ID_IEHISTORY_MIDDLE是预定义的某个菜单项IDQ从该ID开始前后可以创?0个菜单项Q参见下一?/font>
pMenu->InsertMenu( 0, MF_STRING, ID_IEHISTORY_MIDDLE + ++i, strTitle );
}
else
{
pMenu->InsertMenu( 0, MF_STRING, ID_IEHISTORY_MIDDLE - ++i, strTitle );
}
CoTaskMemFree( pszTitle );
m_pTravalLogEntry->Release();
}
}
}
//Ҏ(gu)与当前页面的相对距离来访问历史网?/font>
void CIETravelLog::TravelTo(int nPosition)
{
if ( m_pTravelLogStg == NULL )
{
return;
}
if SUCCEEDED(m_pTravelLogStg->GetRelativeEntry( nPosition, &m_pTravalLogEntry ))
{
m_pTravelLogStg->TravelTo( m_pTravalLogEntry );
}
}
5、用CIETravelLog
假设是在我们自己~写的多H口览器中使用Travel Log。ؓ(f)单v见,我们声明一个CIETravelLog的全局对象g_IETravelLogQ以便在M地方调用。然后在适当的地方,如CMainFrame的TBN_DROPDOWN消息Q工h菜单下拉消息Q处理函数OnDropDown中,d下面的代码,用以创徏览历史菜单Q?/p>
//GetActiveWebBrowserPtrq回zd的浏览器IWebBrowser2接口指针
IETravelLog.SetWebBrowser( GetActiveWebBrowserPtr );
//bForward为true则创?#8220;前进”菜单Q否则创?#8220;后退”菜单
IETravelLog.BuildHistoryMenu( &Menu, 10, bForward);
以下定义单项ID的范_(d)前后共可以容U?0个菜单项Q可Ҏ(gu)实际情况修改?/p>
#define ID_IEHISTORY_FIRST 60200
#define ID_IEHISTORY_MIDDLE 60210
#define ID_IEHISTORY_LAST 60220
d命o(h)处理函数OnTravelHistoryUrl用以响应从ID_IEHISTORY_FIRST到ID_IEHISTORY_LAST的菜单命令?/p>
ON_COMMAND_RANGE(ID_IEHISTORY_FIRST, ID_IEHISTORY_LAST, OnTravelHistoryUrl)
void CMainFrame::OnTravelHistoryUrl(UINT nID /* Command ID */)
{
//nID - ID_IEHISTORY_MIDDLE即ؓ(f)要访问的览历史到当前页面的距离
g_IETravelLog.TravelTo( nID - ID_IEHISTORY_MIDDLE );
}
6、再谈Travel Log
前面我提?#8220;Travel Log接口正日益成为应用程序中的重要接口之一”Q此处加以说明。从微Yq_的开发模式及导向来看Q基于Internet Explorer/WebBrowser Control的应用势必会(x)成ؓ(f)L。在下一代的操作pȝLonghorn中,应用E序界面的描q将完全由XML的一个特化——XAML来完成,而XAML的解析将由浏览器完成。微软说未来应用E序的部|将?x)十分容易,本地应用和基于浏览器的应用之间的差异?x)被逐渐淡化Q而实现这一目标的一个重要表现就是,在将来的操作pȝq_上,应用E序实际上时刻都运行在Internet Explorer中,Internet Explorer在某U程度上来说变成了一个容器?/p>
于是Q扎根于Internet Explorer的Travel Log自然而然地就被整合到了我们的应用E序中。君不见Q我们每天在资源理器和览器上完成的工作,不就是在Travel Log中来来回回地跑吗Q如果所有的应用E序都嵌入到Internet Explorer中运行,那么我们在应用程序中所作的操作便自然得C记录Q?#8220;前进”?#8220;后退”也就很Easy了?/p>
很多软g都已l或多或地开始采用基于Internet Explorer的模式,如Microsoft Money、Microsoft Encarta、Visual Studio.net的安装程序等{,都是很好的范例。所以,q前来_(d)我们的应用E序按这U模式编写(可参考?a >利用览器实现程序界面与实现的分?/a>》)Q不是可以早一点获?#8220;讉K日志的体?#8221;吗?
何乐而不为?/p>
引用地址Q?a >Internet Explorer ~程qͼ八)实现览历史菜单?/font>
Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=308620
Internet Explorer有实在太多没有公布的东西。上一文章《Internet Explorer ~程qͼ六)自定义浏览器上下文菜单》提到的获取“~码”菜单的方法就是利用了览器的上层H口“Shell DocObject View”的未公布的命令ID。本文将要介l的是如何用q个ID?#8220;~码”菜单攑ֈ我们自己的菜单中来(如工h上的“~码”按钮的下拉菜单)?/p>
#define SHDVID_GETMIMECSETMENU 27
......
CComPtr
hr = pcmdTarget->QueryInterface(IID_IOleCommandTarget, (void**)&spCT);
......
// Get the language submenu
hr = spCT->Exec(&CGID_ShellDocView, SHDVID_GETMIMECSETMENU, 0, NULL, &var);
2、原?/strong>
上面指向IOleCommandTarget接口的智能指针spCT是从IDocHostUIHandler::ShowContextMenu的参数pcmdTarget得到的,它其实也可以从HTML文档接口得到Q这是实现的关键?/p>
3、实?/strong>
下面的代码演CZ如何?#8220;~码”菜单攄到我们自q~码菜单上去?/p>
void CMainFrame::OnDropDown( NMHDR* pNotifyStruct, LRESULT* pResult )
{
const UINT CmdID_GetMimeSubMenu = 27;
// Command ID for getting the Encoding submenu
NMTOOLBAR* pNMToolBar = ( NMTOOLBAR* )pNotifyStruct;
CMenu menu;
CMenu* pPopup = 0;
CMyHtmlView *pView = NULL;
m_bIsEncodMenuPopup = false;//标志变量Q用以在WM_INITMENUPOPUP消息处理函数中检?#8220;~码”菜单
switch ( pNMToolBar->iItem )
{
......
case ID_VIEW_ENCODE://按下“~码”按钮
{
m_bIsEncodMenuPopup = true;
VERIFY( menu.LoadMenu( IDR_ENCODE ) );//IDR_ENCODE是预|的“~码”菜单资源Q内含Q意一占位用的菜?/font>
CMyHtmlView = GetActiveMyHtmlView();//查当前是否存在活动的览器视囄?/font>
if ( pView != NULL )
{
LPDISPATCH lpDispatch =pView->GetHtmlDocument();//获得文档指针
if ( lpDispatch != NULL )
{
// Get an IDispatch pointer for the IOleCommandTarget interface.
IOleCommandTarget * pCmdTarget = NULL;
HRESULT hr = lpDispatch->QueryInterface(IID_IOleCommandTarget, (void**)&pCmdTarget);
if ( SUCCEEDED( hr ) )
{
VARIANT varEncSubMenu;
::VariantInit( &varEncSubMenu );
hr = pCmdTarget->Exec( &::CGID_ShellDocView, CmdID_GetMimeSubMenu, OLECMDEXECOPT_DODEFAULT, NULL, &varEncSubMenu );
if ( SUCCEEDED( hr ) )
{
// d“~码”菜单
MENUITEMINFO miiEncoding;
::memset( &miiEncoding, 0, sizeof(MENUITEMINFO) );
miiEncoding.cbSize = sizeof(MENUITEMINFO);
miiEncoding.fMask = MIIM_SUBMENU;
miiEncoding.hSubMenu = reinterpret_cast< HMENU > (varEncSubMenu.byref);
menu.SetMenuItemInfo(0, &miiEncoding, TRUE);//丢掉设计时占位用的菜单,替换?#8220;~码”菜单
}
}
}
}
pPopup = menu.GetSubMenu( 0 );
break;
}
......
}
if ( pPopup != 0 )
{
CRect rc;
::SendMessage( pNMToolBar->hdr.hwndFrom, TB_GETRECT, pNMToolBar->iItem, ( LPARAM )&rc );
rc.top = rc.bottom;
::ClientToScreen( pNMToolBar->hdr.hwndFrom, &rc.TopLeft() );
long lResult = pPopup->TrackPopupMenu( TPM_LEFTALIGN | TPM_LEFTBUTTON | TPM_RETURNCMD, rc.left, rc.top, this );
m_bIsEncodMenuPopup = false;
if ( pNMToolBar->iItem == ID_VIEW_ENCODE )
{
//其余的事教给览器去做,参考?a id="CategoryEntryList.ascx_EntryStoryList_Entries__ctl1_TitleUrl" >Internet Explorer ~程qͼ五)调用IE隐藏的命令(中文版)?/font>
CFindIEWnd FindIEWnd( pView->m_wndBrowser.m_hWnd, "Internet Explorer_Server");
::SendMessage( FindIEWnd.m_hWnd, WM_COMMAND, MAKEWPARAM(LOWORD(lResult), 0x0), 0 );
}
else
{
SendMessage( WM_COMMAND, MAKEWPARAM(LOWORD(lResult), 0x0), 0 );
}
}
*pResult = TBDDRET_DEFAULT;
}
void CMainFrame::OnInitMenuPopup(CMenu* pPopupMenu, UINT nIndex, BOOL bSysMenu)
{
CMDIFrameWndEx::OnInitMenuPopup(pPopupMenu, nIndex, bSysMenu);
if ( m_bIsEncodMenuPopup )
{
//默认情况?#8220;~码”的所有菜单项都是Disabled的,在此修改其状态ؓ(f)Enabled
for ( UINT i=0; i
{
pPopupMenu->EnableMenuItem( pPopupMenu->GetMenuItemID( i ), MF_ENABLED | MF_BYCOMMAND );
}
}
}
q样一来,原本只在览器上下文菜单中出现的“~码”菜单出现在了我们自q工具条按钮下拉菜单上Q无需更多的处理,菜单状态的改变Q编码的讄{,一切都教给览器自己去完成了?/p>
1、概q?br> Internet Explorer提供了非常开发的接口Q开发h员不仅可以把其浏览器核心嵌入应用E序Q还可以通过各种接口以实现更深层的控制。本文就介l对览器进行高U控制的话题之一——自定义上下文菜单?/p>
2、最单的情况
自定义的IE及WebBrowser的上下文菜单Q最单的方式是在注册表的HKEY_CURRENT_USER\Software\Microsoft\Internet Explorer\MenuExt下添加自定义的键|步骤如下Q?br> 1) d一个新的键Q其名称即ؓ(f)来昄在上下文菜单中的菜单名Uͼ如:(x)
HKEY_CURRENT_USER\Software\Microsoft\Internet Explorer\MenuExt\&Google Search
2) 新增的键的默认D|ؓ(f)一个包含脚本的|页的URLQ或文g路径全名Q,该网中的脚本将在用L(fng)M下文菜单中的“Google Search”后被览器执行?/p>
3)在新增的键下q可以新Z个二q制值ContextsQ用以指定我们新增的菜单w对特定的|页对象是否出现Q其取值可以是如下值的l合Q逻辑或)
Context Value
Default 0x1
Images 0x2
Controls 0x4
Tables 0x8
Text selection 0x10
Anchor 0x20
4) q可以徏立一个DWORDcd的Flagsƈ其D|ؓ(f)0x01Q这得前q脚本在一个模态窗口中执行Q就好像是通过window.showModalDialog调用的,但不同的是在脚本中仍然可以访问window对象?br> 5) 实例脚本如下Q?/p>
通过修改注册表自定义菜单的方法适用于Internet Explorer和W(xu)ebBrowserQ也h良好的扩展性。但我们如果希望执行的是不仅仅是脚本Q二是自qE序中代码,q种Ҏ(gu)׃适用了?/p>
3、用完全自定义的菜?br>1) IDocHostUIhandler接口提供了一个ShowContextMenuҎ(gu)Q在需要显CZ下文菜单之前QMSHTML引擎׃(x)调用实现了IDocHostUIHandler接口?br>宿主E序的ShowContextMenuҎ(gu)?/p>
HRESULTIDocHostUIHandler::ShowContextMenu(
DWORD dwID,
POINT *ppt,
IUnknown *pcmdtReserved,
IDispatch *pdispReserved
);
dwID参数的意义与Contexts的组合类|ppt单的弹出点屏q坐标;pcmdtReserved接口指向 IOleCommandTarget接口Q可用于网对象的状态和执行命o(h){操作。pdispReserved在IE5以上版本中指向的是网对象的 IDispatch接口Q用以区分不同对象,比如我们可以q样来获得网对象的指针Q?/p>
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();
}
如果我们在该Ҏ(gu)中返回S_OKQ则告诉MSHTML我们用自q菜单Q界面)Q如果是S_FALSEQ则弹出默认的菜单?/p>
2) 实现
原理清楚之后Q实现v来非常简单,和弹Z般的菜单没什么两P举例如下Q显CZ框架?#8220;文g菜单”Q?/p>
HRESULT CMyHtmlView::OnShowContextMenu(DWORD dwID, LPPOINT ppt, IUnknown * pcmdtReserved, IDispatch *)
{
// 载入主菜?br> HMENU hMenuParent = ::LoadMenu( ::AfxGetInstanceHandle(), MAKEINTRESOURCE(IDR_MAINFRAME) );
if (hMenuParent)
{
HMENU hMenu = ::GetSubMenu( hMenuParent, 0 ); // 取得“文g”子菜?br> if (hMenu)
{
// 昄菜单
TrackPopupMenuEx( hMenu,
TPM_LEFTALIGN | TPM_TOPALIGN,
ppt->x,
ppt->y,
::AfxGetMainWnd()->m_hWnd,
NULL );
}
::DestroyMenu( hMenuParent );
}
return S_OK;
}
4、自定义标准上下文菜?br>1) 原理
更多的时候我们希望能在浏览器原来菜单的基上作一些修改,如删?#8220;查看源文?#8221;Q添加自q菜单,{等Q而不是完全不要原始的菜单Q怎么办呢Q借助MSDN提供的例子,我们来看看:(x)
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
CComPtr
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);//取得览器窗口句?br> 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);//删除“查看源文?#8221;菜单?br> // Show shortcut menu
int iSelection = ::TrackPopupMenu(hMenu,
TPM_LEFTALIGN | TPM_RIGHTBUTTON | TPM_RETURNCMD ,//q回用户选择的菜单命令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_Serverq行内部处理?br> FreeLibrary(hinstSHDOCLC);
return S_OK;
}
? 上面的例子我们看出,基本的方法就是根?#8220;shdoclc.dll”文g中的菜单资源建立菜单Q再通过来自pcmdTarget? IOlcCommandTarget接口获得“~码”菜单以及HKEY_CURRENT_USER\Software\Microsoft\ Internet Explorer\MenuExt下的定义扩展菜单Q然后以TPM_RETURNCMD标志调用TrackPopupMenu? TrackPopupMenuEx弹出菜单Qƈ返回的菜单命o(h)ID教给览器窗口进行处理。这U方法可以调用许多通过览器无法直接调用的命o(h)和对话框 Q参阅:(x)?a >Internet Explorer ~程qͼ五)调用IE隐藏的命?/font>》)?/font>
所以,我们只需要在弹出菜单之前做一些自定义操作卛_辑ֈ修改默认菜单的目的,如上面代码中q删除?#8220;查看源文?#8221;菜单V?/font>
2) 问题
如果我们不仅仅是删除默认的菜单项或是修改了默认的菜单,q添加了自己的菜单项Q会(x)出现什么情况呢Q由于用了cM于MFC中UpdateUI的机Ӟ遇到不认识的CommandIDQ浏览器׃(x)其状态设|ؓ(f)DisabledQ所以我们自己添加的菜单是无法被选择的?br>?
以想刎ͼ如果把菜单状态设|ؓ(f)EnabledQƈ通过TPM_RETURNCMD标志调用TrackPopupMenu?
TrackPopupMenuExQ再把返回的命o(h)ID教给合适的H口Q如L架窗口)d理不p了。关键点在于如何把菜单状态设|ؓ(f)
Enabled?/font>
3) 实现
解决的办法是截获 WM_INITMENUPOPUP 消息Q在菜单创徏以后Q尚未显CZ前修改菜单项状态,那浏览器没有办法了。截获WM_INITMENUPOPUP消息则可使用子类化(subclassQ的技术,前面通过IOleWindow接口我们得到了浏览器H口的句柄hwndQ则可以q样做:(x)
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( 自定义的菜单命o(h)ID, MF_ENABLED | MF_BYCOMMAND );
::CheckMenuItem( 自定义的菜单命o(h)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?br>......
// subclass览器窗?br>g_lpPrevWndProc = (WNDPROC)::SetWindowLong(hwnd, GWL_WNDPROC, (LONG)CustomMenuWndProc);
//m_SubclassWnd.SubclassWindow( hwnd );//MFC中用此方法更?/font>
// Show shortcut menu
int iSelection = ::TrackPopupMenu(hSubMenu,
TPM_LEFTALIGN | TPM_RIGHTBUTTON | TPM_RETURNCMD,
ppt->x,
ppt->y,
0,
hwnd,
(RECT*)NULL);
// Unsubclass览器窗?br>::SetWindowLong(hwnd, GWL_WNDPROC, (LONG)g_lpPrevWndProc);
g_lpPrevWndProc = NULL;
//m_SubclassWnd.UnsubclassWindow();
if (iSelection == 自定义的菜单命o(h)ID )
{
::SendMessage( ::AfxGetMainWnd()->m_hWnd, WM_COMMAND, MAKEWPARAM(LOWORD(lResult), 0x0), 0 );
}
else
{
LRESULT lr = ::SendMessage(hwnd, WM_COMMAND, iSelection, NULL);
}
......
}
在MFC 中则更ؓ(f)方便Q从CWndl承一个窗口类Q假设ؓ(f)CWebBrowserSubclassWndQؓ(f)CMyHtmlViewd一? CWebBrowserSubclassWndcd的成员变量m_SubclassWndQ在子类化和去除子类化时调用 m_SubclassWnd.SubclassWindow( hwnd )和m_SubclassWnd.UnsubclassWindow()卛_。相应的WM_INITMENUPOPUP消息处理函数如下所C:(x)
void CWebBrowserSubclassWnd::OnInitMenuPopup(CMenu* pPopupMenu, UINT nIndex, BOOL bSysMenu)
{
CWnd::OnInitMenuPopup(pPopupMenu, nIndex, bSysMenu);
pPopupMenu->EnableMenuItem( 自定义的菜单命o(h)ID, MF_ENABLED | MF_BYCOMMAND );
pPopupMenu->CheckMenuItem( 自定义的菜单命o(h)ID, MF_BYCOMMAND);
}
下面的图片显CZ?#8220;文字大小”菜单Ҏ(gu)加到“~码”菜单的下面的效果?/font>
5、新的问?br> 看完上面的代码,我们又自然地惛_览器编E中的另一个问题,那就?#8220;~码”菜单。我们指定,手动建立一?#8220;~码”菜单是比较麻烦的事,而且很难做到与浏览器上下文菜单上?#8220;~码”菜单一L(fng)效果。何不用上q的Ҏ(gu)让浏览器自己建立“~码”菜单和处理相应的命o(h)呢?
具体实现L(fng)下一文章《Internet Explorer ~程qͼ七)完美?#8220;~码”菜单?/font>
参考资?/strong> Q?br>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
2、契?br>
MSDN中有一叫“WebBrowser Customization”的文章,其中介绍了通过IDocHostUIHandler.ShowContextMenuҎ(gu)自定义WebBrowser上下文菜单的Ҏ(gu)?br>
?
原理是从“shdoclc.dll”的资源中创徏菜单Q作一些修改之后用TrackPopupMenu函数Q注意在标志中包?
TPM_RETURNCMDQ将菜单弹出Q然后把q回的Command ID发送给“Internet Explorer_Server”H口q行处理?/p>
...... // 昄菜单 int iSelection = ::TrackPopupMenu(hMenu, TPM_LEFTALIGN | TPM_RIGHTBUTTON | TPM_RETURNCMD, ppt->x, ppt->y, 0, hwnd, (RECT*)NULL); // 发送Command ID到外壳窗?br> LRESULT lr = ::SendMessage(hwnd, WM_COMMAND, iSelection, NULL); ...... |
好,如果扑ֈ所有上下文菜单的Command IDQ不可以随时调用了Q确实是q样的?br>
3、实?br> 用eXeScope之类应用E序资源探烦器打开“shdoclc.dll”便可以在菜单资源下找C下文菜单的设计,如下图:(x)
我们要做的,是这些ID发送到“Internet Explorer_Server”H口q行处理。问题是WebBrowser其实是一个OLE容器Q我们用的CHtmlView又是更外层的装Q他? 的m_hWnd成员变量q不是IEH口的句柄,如何扑ֈ我们需要的句柄呢?L(fng)下面的图Q?/p>
Ҏ(gu)图中昄的从属关p,摸瓜Q最内层的窗?#8220;Internet Explorer_Server”的句柄就是我们需要的东西。ؓ(f)了简化问题,我这里用了来自MSDN Magazine资深专栏撰稿人Paul Dilascia的CFindWndc,非常好用?/p>
//////////////////////////////////////////////////////////////// // MSDN Magazine -- August 2003 // If this code works, it was written by Paul DiLascia. // If not, I don't know who wrote it. // Compiles with Visual Studio .NET on Windows XP. Tab size=3. // // --- // This class encapsulates the process of finding a window with a given class name // as a descendant of a given window. To use it, instantiate like so: // // CFindWnd fw(hwndParent,classname); // // fw.m_hWnd will be the HWND of the desired window, if found. // class CFindWnd { private: ////////////////// // This private function is used with EnumChildWindows to find the child // with a given class name. Returns FALSE if found (to stop enumerating). // static BOOL CALLBACK FindChildClassHwnd(HWND hwndParent, LPARAM lParam) { CFindWnd *pfw = (CFindWnd*)lParam; HWND hwnd = FindWindowEx(hwndParent, NULL, pfw->m_classname, NULL); if (hwnd) { pfw->m_hWnd = hwnd; // found: save it return FALSE; // stop enumerating } EnumChildWindows(hwndParent, FindChildClassHwnd, lParam); // recurse return TRUE; // keep looking } public: LPCSTR m_classname; // class name to look for HWND m_hWnd; // HWND if found // ctor does the work--just instantiate and go CFindWnd(HWND hwndParent, LPCSTR classname) : m_hWnd(NULL), m_classname(classname) { FindChildClassHwnd(hwndParent, (LPARAM)this); } }; |
再写一个函数InvokeIEServerCommandQ调用就很方便了Q《Internet Explorer ~程qͼ四)“d到收藏夹”对话框》中最后给出的Ҏ(gu)是从这里来的?/p>
void CMyHtmlView::InvokeIEServerCommand(int nID) { CFindWnd FindIEWnd( m_wndBrowser.m_hWnd, "Internet Explorer_Server"); ::SendMessage( FindIEWnd.m_hWnd, WM_COMMAND, MAKEWPARAM(LOWORD(nID), 0x0), 0 ); } |
4、Command IDs
Ҏ(gu)有的Command ID逐一试后我们发玎ͼ(x)
1)不是所有的Command ID都可以用上面的方法调用;
2)不是所有的Command ID都是?#8220;Internet Explorer_Server”H口处理Q?br> 3)有一些Command ID是由上一U窗?#8220;Shell DocObject View”处理?br> 所以我们还需要写一个函数?/p>
void CMyHtmlView::InvokeShellDocObjCommand(int nID) { CFindWnd FindIEWnd( m_wndBrowser.m_hWnd, "Shell DocObject View"); ::SendMessage( FindIEWnd.m_hWnd, WM_COMMAND, MAKEWPARAM(LOWORD(nID), 0x0), 0 ); } |
调用文章开头提到的“导入/导出”对话框可以这h做:(x)
void CDemoView::OnImportExport() ?Internet Explorer_Server"H口处理的Command ID: ?Shell DocObject View"H口处理的Command ID: |
5、Refresh
熟?zhn)TEmbeddedWB的读者可能注意到了ID_IE_CONTEXTMENU_REFRESH(6042)q个IDQ在TEmbeddedWB中给Z一个当|页h时触发的OnRefresh事gQ其中的关键代码如下Q?/p>
...... if Assigned(FOnRefresh) and ((nCmdID = 6041 { F5}) or (nCmdID = 6042 { ContextMenu}) or (nCmdID = 2300)) then begin FCancel := False; FOnRefresh(self, nCmdID, FCancel); if FCancel then Result := S_OK; end; ...... |
6、需要注意的问题
1)用InvokeIEServerCommand
(ID_IE_CONTEXTMENU_ADDFAV)调用“d到收藏夹”对话框时需要注意的是,IE接收?
ID_IE_CONTEXTMENU_ADDFAV命o(h)时是对网中当前被选中的链接来执行“d到收藏夹”操作的,如果没有选中的链接,才是当前网?
d到收藏夹?br>
2)新徏IEH口。这是浏览器~程中的N之一Q即从当前窗口新Z个Internet
ExplorerH口Q完全复制当前页的内容(包括“前进”?#8220;后退”的状态)Q这可以通过InvokeShellDocObjCommand
(ID_IE_FILE_NEWWINDOW)来实现?br>
3)昄IE的版本信息。调用InvokeShellDocObjCommand(ID_IE_HELP_ABOUTIE)Q如下:(x)
4)InvokeShellDocObjCommand(ID_IE_FILE_PRINT)调出?#8220;打印”对话框是非模态的Q我们不太清?
Microsoft的设计意图,我认?#8220;打印”对话框应该是模态的Q,昄模态窗口的Ҏ(gu)请参考我的另一文章《利用WH_CBT
Hook非模态对话框昄为模态对话框?/p>
2、IShellUIHelper接口
微Y专门提供了一个接口IShellUIHelper来实现对Windows Shell API一些功能的讉KQ将链接d到收藏夹也是其中之一Q就是下面的AddFavorite函数?/p>
HRESULT IShellUIHelper::AddFavorite(BSTR URL, VARIANT *Title); |
实例代码如下Q?/p>
void CMyHtmlView::OnAddToFavorites() if (SUCCEEDED(hr)) pShellUIHelper->AddFavorite(strURL.AllocSysString(), &vtTitle); |
我们注意到这里的“AddFavorite”函数q没有像“DoOrganizeFavDlg”那样需要一个父H口句柄。这也导致与在IE中打
开不同Q通过IShellUIHelper接口昄出来?#8220;d到收藏夹”对话框是“非模?#8221;的,有一个独立于我们应用E序的Q务栏按钮Q这使我们的览
器显得非怸专业Q我是个q求完美的hQ这也是我的览器迟q不能发布的原因之一Q?br>
于是我们很自然地惛_“shdocvw.dll”中除?#8220;DoOrganizeFavDlg”外,应该q有一个类似的函数Q可以传入一个父H口句柄用以昄模态窗口,也许像q样Q?/p>
typedef UINT (CALLBACK* LPFNADDFAV)(HWND, LPTSTR, LPTSTR); |
事实上,q样的函数确实存在于“shdocvw.dll”中,那就?#8220;DoAddToFavDlg”?/p>
3、DoAddToFavDlg函数
“DoAddToFavDlg”函数也是“shdocvw.dll”暴露出来的函C一Q其原型如下Q?/p>
typedef BOOL (CALLBACK* LPFNADDFAV)(HWND, TCHAR*, UINT, TCHAR*, UINT,LPITEMIDLIST); |
W一个参数正是我们想要的父窗口句柄,W二和第四个参数分别是初始目录(一般来说就是收藏夹目录Q和要添加的链接的名字(比如|页?
TitleQ,W三和第五个参数分别是第二和W四两个~冲区的长度Q而最后一个参数则是指向与W二个参数目录相关的item identifier
list的指?PIDL)。但最奇怪的是这里ƈ没有?#8220;AddFavorite”函数一L(fng)链接URLQ那链接是怎样d的呢Q答案是“手动创徏”?br>
W二个参数在函数调用q回后会(x)包含用户?#8220;d到收藏夹”对话框中选择或创建的完整链接路径名(?#8220;X:\XXX\mylink.url”Q,我们根
据这个\径和|页的URL来创建链接,代码如下Qؓ(f)化,此处省去?shdocvw.dll"是否已在内存中的代码Q参见《Internet
Explorer ~程qͼ三)“整理收藏?#8221;对话框》)Q?/p>
void CMyHtmlView::OnFavAddtofav() HMODULE hMod = (HMODULE)LoadLibrary("shdocvw.dll"); if (SHGetSpecialFolderPath(NULL, szPath, CSIDL_FAVORITES, TRUE) && TCHAR szURL[MAX_PATH]; BOOL bOK = lpfnDoAddToFavDlg(m_hWnd, szPath, if (bOK) |
实现CreateInternetShortcut函数创徏Internet快捷方式Q可以用dINI文g的方法,但更好的则是利用IUniformResourceLocator接口?/p>
HRESULT CMyHtmlView::CreateInternetShortcut(LPCSTR pszURL, LPCSTR pszURLfilename, CoInitialize(NULL); IUniformResourceLocator *pHook; hres = CoCreateInstance (CLSID_InternetShortcut, NULL, CLSCTX_INPROC_SERVER, if (SUCCEEDED (hres)) // Query IShellLink for the IPersistFile interface for if (SUCCEEDED (hres)) // Set the path to the shortcut target. hres = psl->SetIconLocation(szIconFile,nIndex); if (SUCCEEDED (hres)) if (SUCCEEDED (hres)) // Save the shortcut via the IPersistFile::Save member function. // Release the pointer to IPersistFile. // Release the pointer to IShellLink. } |
好,上面的方法虽焉烦一点,但ȝ解决?#8220;模态窗?#8221;的问题,使得我们的程序不至于让用户鄙视。但是问题又来了Q我们发?#8220;允许脱机使用”是Disabled的,?#8220;自定?#8221;也就无从谈v了,管90%的h都没有用过IE提供的脱机浏览?br>
N我们的希望要破灭吗?我们一斚w惛_调用“AddFavorite”函数一L(fng)不必手动创徏链接Q一斚w又要模态显C窗口,像IE那样Q还能自定义脱机览?/p>
3、脚本方?/p>
许多|页上都?x)有一个按钮或链接“d本页到收藏夹”Q实际上通过下面的脚本显C模态的“d到收藏夹”对话框将|页加入到收藏夹?/p>
window.external.AddFavorite(location.href, document.title); |
q里的external对象是WebBrowser内置的COM自动化对象,以实现对文档对象模型QDOMQ的扩展Q我们也可以通过 IDocHostUIHandler实现自己的扩展).查阅MSDN可以得知external对象的的Ҏ(gu)与IShellUIHelper接口提供的方? 是一L(fng)。我们有理由怿QIShellUIHelper提供了对WebBrowser内置的external对象的访问,如果在适当的地方创? IShellUIHelper接口的实例,也许调用“AddFavorite”函数昄出来的就是模态对话框了。问题是我们q没有找到这L(fng)地方?/p>
从上面的脚本Q我们很自然地又惛_另一个方法。如果能够让|页来执行上面的脚本Q岂不是问题p决了Q说做就做,如下Q?/p>
void CMyHtmlView::OnFavAddtofav() void CMIEView::ExecScript(CString strjs) if ( pHTMLWnd != NULL ) |
先从CHtmlView获得文档的父H口window对象的指针,再调用其Ҏ(gu)execScript来执行脚本(事实上可以执行Q意的脚本Q? 试验发现Q这个方法非常有效,不仅H口是模态的Q而且不需要手动创建链接,更重要的?#8220;允许脱机使用”?#8220;自定?#8221;按钮也可以用了?/p>
4、问题仍旧没有解?/p>
执行脚本的方式看h有效Q可一旦我们的E序实现了IDocHostUIHandler接口对WebBrowserq行高控制Q就?x)发C? 执行的脚本包含有?#8220;external”对象的调用,׃(x)出现“找不到对?#8221;的脚本错误。原因是当MSHTML解析引擎Qƈ非WebBrowserQ检? 到宿dCIDocHostUIHandler接口Q就?x)调用其GetExternalҎ(gu)以获得一个用以扩展DOM的自动化接口的引用?/p>
HRESULT IDocHostUIHandler::GetExternal(IDispatch **ppDispatchQ?/p> |
但有时候我们ƈ没有惌扩展DOMQ同时我们还希望WebBrowser使用它自qDOM扩展。糟p的是GetExternalҎ(gu)的文档中 说这U情况下必须把ppDispatch讄为NULLQ换句话_(d)W(xu)ebBrowserq它内置的external对象也不用了Q那我们? window.external.AddFavorite变得无处ؓ(f)家了?/p>
我曾多方试WebBrowser内置的external对象扑և来,虽然都没有成功,但是解决问题的方法却被我扑ֈ了?/p>
5、完的Ҏ(gu)
WebBrowser内置的external对象我们虽然找不刎ͼ但它肯定存在Q我们只要想办法让WebBrowser自己完成对其调用卛_? 实现非常单,扑ֈWebBrowser中包含的“Internet Explorer_Server”H口的句柄,发一个消息就完成了。下面的代码中假设m_hWndIE是“Internet Explorer_Server”H口的句柄?/p>
#define ID_IE_ID_ADDFAV 2261 |
试一试成果,是不是和在Internet Explorer中选择“d到收藏夹”的效果一模一栗?/p>
至于Z么这样做Q后l文章再说?/p>
1、整理收藏夹
调用“整理收藏?#8221;对话框(如下Q,基本上来说都用的是同一个方法,卌?#8220;shdocvw.dll”中的“DoOrganizeFavDlg”函数Q把父窗口句柄和收藏夹\径作为参C入即可?/p>
2、代?/strong>
代码实例如下所C,值得注意的是?#8220;shdocvw.dll”的处理,为避免重复调用,应该先检查其是否已经在内存中?/p>
void CMyHtmlView::OnFavOrganizefav() bool bResult = false; HMODULE hMod = ::GetModuleHandle( _T("shdocvw.dll") ); if (hMod == NULL)//如果"shdocvw.dll"未载入则蝲入之 if (hMod == NULL) |
MessageBox( _T("The dynamic link library ShDocVw.DLL cannot be found."), if (lpfnDoOrganizeFavDlg == NULL) TCHAR szPath [ MAX_PATH ]; hr = ::SHGetSpecialFolderPath( m_hWnd, szPath, CSIDL_FAVORITES, TRUE ); bResult = (*lpfnDoOrganizeFavDlg) ( m_hWnd, szPath ) ? true : false; ::FreeLibrary( hMod ); if (lpfnDoOrganizeFavDlg == NULL) TCHAR szPath [ MAX_PATH ]; hr = ::SHGetSpecialFolderPath( m_hWnd, szPath, CSIDL_FAVORITES, TRUE ); |
bResult = (*lpfnDoOrganizeFavDlg) ( m_hWnd, szPath ) ? true : false; return;
|
3、讨?/strong>
实际上,?#8220;DoOrganizeFavDlg”函数的原型声明我们可以看刎ͼ׃需要一个\径,所?#8220;整理收藏?#8221;对话框其实不仅可以用来整 理收藏夹Q还可以整理盘上的目录。而且所谓的整理也不q是提供了一个对话框使用L(fng)h比较方便而已Q和直接在资源管理器中整理没有实质性的差别。因? 调用“整理收藏?#8221;对话框的Ҏ(gu)从IE4.0开始就没有变过Q除了对话框的布局有所改变?/p>
typedef UINT (CALLBACK* LPFNORGFAV)(HWND, LPTSTR); |
IE 4.0?#8220;整理收藏?#8221;对话?br>
IE 4.0?#8220;整理收藏?#8221;对话框(原本的设计)
“d到收藏夹”׃同了Q?#8220;DoAddToFavDlg”函数不再?#8220;DoOrganizeFavDlg”函数一样对所有IE的版本都适用?/p>
参考资料:(x)
MSDN: Adding Internet Explorer Favorites to Your Application
在Adobe Reader/Acrobat的属性设|窗口中Q我们可以找?#8220;Display PDF in browser”的选项Q如果勾上,则Navigate("xxx.pdf")会(x)以嵌入的方式在IE中浏览PDF文gQ否则在独立的Adobe Reader/AcrobatH口中浏览。但在Office?#8220;选项”对话框中我们找不到这L(fng)讄?/p>
问题Q如何在自己的浏览器中控制Officeq类Ole Servers的打开方式Q?br>
{案Q修Ҏ(gu)件夹选项Q或修改注册表?br>
Ҏ(gu)1、如下所C,从控刉板中打开“文g?#8221;选项Q在“文gcd”属性页上找到相应的文g后缀名,?#8220;DOC”Q点?#8220;高”按钮Q在弹出?#8220;~辑文gcd”对话框中?#8220;在同一H口中浏?#8221;的选项Q如果勾上,则以嵌入IE的方式打开文档Q否则在独立H口中打开?br>
Ҏ(gu)2、直接修Ҏ(gu)册表?br>
?#8220;HKEY_LOCAL_MACHINE\SOFTWARE\Classes”键gQ保存了各种文gcd的注册信息,以Office文档ZQ与文档相关键值如下?br>
文档cd 键?br>
Microsoft Excel 7.0 worksheet Excel.Sheet.5
Microsoft Excel 97 worksheet Excel.Sheet.8
Microsoft Excel 2000 worksheet Excel.Sheet.8
Microsoft Word 7.0 document Word.Document.6
Microsoft Word 97 document Word.Document.8
Microsoft Word 2000 document Word.Document.8
Microsoft Project 98 project MSProject.Project.8
Microsoft PowerPoint 2000 document PowerPoint.Show.8
如果我们要修改Word文档的打开方式Q,则在“HKEY_LOCAL_MACHINE\SOFTWARE\Classes\
Word.Document.8”下新Z个名?#8220;BrowserFlags”Q类型ؓ(f)“REG_DWORD”的子键|如果讄其gؓ(f)“8”Q则在独?
的窗口中打开Word文档Q否则在嵌入IE的WordH口中打开文档?br>
注:(x)Microsoft Excel 7.0 worksheetE有不同Q应讄BrowserFlags的gؓ(f)“9”方可在独立的H口中打开文档?br>
光正是多窗口浏览器全面开q日子Q无奈手头事情太多,我的作品Multiple iExplorer也一直未能问世,至今遗憾。后来常与GoSurf的作者交学?fn),替他解决了不问题,也从他那里学到许多。如今GoSurf有了比较 固定的用L(fng)Q有我一份功劻I是一U安慰吧Q他也一度在GoSurf官方主页上将我列为核心技术支持,但我后来因ؓ(f)工作和学?fn)的关系很少再和他联p,? 在惭愧?/p>
回头再看当时的文章,错误实在不少Q认识也比较薄Q有些问题更是一直没有得到解冻I所以我觉得有必要在前文的基上,q旉我U篏的关于Internet Explorer~程的问题比较完整地写出来,希望对自己有个交代,对大家有一些帮助?/p>
是ؓ(f)序?/p>
Internet Explorer~程qͼ一QWebBrowserq是WebBrowser_V1
你的机器上L存在着“?#8221;个WebBrowserQ一个叫WebBrowserQ另一个叫WebBrowser_V1Q其CLASSID如下Q?/p>
CLASS_WebBrowser: TGUID = '{8856F961-340A-11D0-A96B-00C04FD705A2}'; CLASS_WebBrowser_V1: TGUID = '{EAB22AC3-30C1-11CF-A7EB-0000C05BAE0B}'; |
它们分别对应的接口是IWebBrowser2和IWebBrowser。问题是我们该用哪一个呢Q?br> 按照微Y的推荐,应该量使用 前者,因ؓ(f)后者是为兼容Internet Explorer 3.x而保留的Q尽它能够响应来自Internet Explorer 3.x?.x?.x?.x的事ӞQ相应的IWebBrowser和IWebBrowserApp接口也应抛弃?/p>
׃Internet Explorer 3.xq代久远Q导致WebBrowser_V1提供的事件少得可怜,但值得一提的是它提供的两个事件OnNewWindow? OnFrameBeforeNavigate有着与OnBeforeNavigate几乎相同的参敎ͼ(x)
OnBeforeNavigate( BSTR URL, long Flags, BSTR TargetFrameName, VARIANT* PostData, BSTR Headers, BOOL FAR* Cancel) |
OnNewWindow( OnFrameBeforeNavigate( |
所以用WebBrowser_V1使得我们的浏览器在有新窗口打开时能够轻易捕捉到其URL及相关的数据Q如果将Processed讄为TRUEQ则可取消新H口的弹出。同P处理Frame也比在WebBrowser中来得容易?/p>
但WebBrowser_V1的致命弱Ҏ(gu)它不支持高接口Q如IDocHostUIHandlerQ即便我们实C IDocHostUIHandler接口Q也不会(x)被WebBrowser_V1调用。所以希望在自己的浏览器中实现XP的界面主题、扩展IE的DOM QDocument Object ModelQ等高控制的话Q就肯定不能选择WebBrowser_V1了?/p>
处理新窗口实在是很麻烦的一件事Q不知道微YZ么在新版本的OnNewWindow2事g中去掉了URLq样的参敎ͼ而且OnNewWindow2事g不能完全捕捉到所有的新窗口打开。但如果安装了Windows XP SP2的话Q好处又回来了?/p>
Windows XP SP2对Internet Explorer
6作了升Qƈ且提供了一个新的事件OnNewWindow3Q它在OnNewWindow2事g之前发生Q也包含了让我们能够加以qo(h)处理的新H口?
URL{参敎ͼ再加上INewWindowManager接口Q就是实现Windows XP SP2中过滤广告窗口功能的基础?/p>