IE控件编程)

2. IE控件中HTML Page能收到键盘事件

最近做了一个小程序,用到了IE控件,但是里面的HTML Page 无法响应onkeydown等事件。于是建立了一个WTL的工程,发现了关键代码:

复制代码
BOOL PreTranslateMessage(MSG * pMsg)
{
    
if ((pMsg -> message  <  WM_KEYFIRST  ||  pMsg -> message  >  WM_KEYLAST)  &&
    (pMsg
-> message  <  WM_MOUSEFIRST  ||  pMsg -> message  >  WM_MOUSELAST))
        
return  FALSE;

    
//  give HTML page a chance to translate this message
     return  (BOOL)SendMessage(WM_FORWARDMSG,  0 , (LPARAM)pMsg);
}
复制代码

网上也有其他方案,但是感觉这种最地道。

现在能收到事件了,但是你可能会发现,鼠标不能选择上面的文字,并非是鼠标消息没有传递过来,而是--

 

注意这个方法,put_DocHostFlags和属性 & ~DOCHOSTUIFLAG_DIALOG,是的,把它取消掉即可。否则你的IE呈现出来的页面,真的像Dialog一样了。
 

 

3. IE控件怎么得知404,500等Http Code。 

【注:我这里说的IE控件,泛指MFC里面的CHtmlView, IHtmlDocument等等相关的,准确说是IE编程相关的。】

今天试图在DISPID_DOCUMENTCOMPLETE事件中,得到HTML的内容等等,来判断是不是断网了等情况,这太不地道了,IE6,IE7,IE8显示的内容都不一样。我又找不到HTTP Code,而且那个ReadyState没有什么用。看了100篇文章都没有找到答案,最后翻了一下头文件,在DISPID_DOCUMENTCOMPLETE附近,还有一个叫做DISPID_NAVIGATEERROR的事件。

整个代码看起来像是这个样子。

复制代码
CComPtr < IWebBrowser2 >  spWebBrowser2;
HRESULT hRet 
=  QueryControl(IID_IWebBrowser2, ( void ** ) & spWebBrowser2);
if (SUCCEEDED(hRet))
{
    HRESULT hr 
=  IDispEventSimpleImpl < 1 , CMyHtmlView,  & DIID_DWebBrowserEvents2 > ::DispEventAdvise(spWebBrowser2,  & DIID_DWebBrowserEvents2);

    
if (FAILED(hr))
        ATLASSERT(FALSE);
}
复制代码

那么CMyHtmlView就要继承

IDispEventSimpleImpl<1, CMyHtmlView, &DIID_DWebBrowserEvents2>

 

SINK_ENTRY_INFO( 1 , DIID_DWebBrowserEvents2, DISPID_DOCUMENTCOMPLETE, OnDocumentComplete,  & DocComplete_Info)
SINK_ENTRY_INFO(
1 , DIID_DWebBrowserEvents2, DISPID_NAVIGATEERROR, OnNavigateError,  & NavError_Info)
//  NOTICE stdcall
void  __stdcall OnDocumentComplete(LPDISPATCH pDisp, VARIANT FAR *  URL);
void  __stdcall OnNavigateError(LPDISPATCH pDisp, VARIANT * , VARIANT * , VARIANT * , VARIANT_BOOL * );

参考文章:

 

http://blog.sina.com.cn/s/blog_465c136b010008tn.html
http://social.msdn.microsoft.com/Forums/zh-CN/ieextensiondevelopment/thread/eff1e37c-7cf9-41ad-aac1-c5516e5a45db
【NOTICE:文档中有些参数的顺序,有待考证。】

 

 

 

OnNavigateError中有个参数是ErrorCode,值类型为VT_I4,实际一些错误值,直接指向了404这些code~。

 

http://msdn.microsoft.com/en-us/library/bb268233%28VS.85%29.aspx

最后,最后一个参数,*bCancel  = VARIANT_TRUE;的话,不会再次进入OnDocumentComplete,否则还是会进去溜一圈的。

======================================================================================== 

4. IE控件保存选中的图片

这绝对是个稍微复杂一点的事情了:

4.1 得到图片

复制代码
//  1. 假设我们在CHtmlView一类的容器中,得到IWebBrowser2接口
CComPtr < IWebBrowser2 >  spWebBrowser2;
HRESULT hRet 
=  QueryControl (IID_IWebBrowser2, ( void ** ) & spWebBrowser2);

//  2. 得到IHTMLDocument2接口
CComPtr < IDispatch >  spDisp;
spWebBrowser2
-> get_Document( & spDisp);
CComQIPtr
< IHTMLDocument2 >  spDoc2(spDisp);

//  3. 得到IHTMLWindow2接口
CComPtr < IHTMLWindow2 >  spWindow2;
spDoc2
-> get_parentWindow( & spWindow2);

//  4. 得到IHTMLEventObj接口
CComPtr < IHTMLEventObj >  spEvent;
spWindow2
-> get_event( & spEvent);

//  5.得到IHTMLElement接口
CComPtr < IHTMLElement >  spElem;
spEvent
-> get_srcElement( & spElem);

//  6. 判断是不是tag
spElem -> get_tagName( & bstrTagName);
//  TODO: bstrTagName == img ?

//  7. 得到IHTMLImgElement接口
CComQIPtr < IHTMLImgElement >  spImg(spElem);
复制代码

做完一半的工作了。

4.2 拷贝到剪切板

复制代码
//  1. 从IHtmlDocument2接口获得的IHTMLElement接口
CComPtr < IHTMLElement >  spBody;
spDoc2
-> get_body( & spBody);

//  2. "转"为IHTMLElement2接口,COM就是麻烦
CComPtr < IHTMLElement2 >  spBody2;
spBody
-> QueryInterface(IID_IHTMLElement2, ( void ** ) & spBody2);  //  CComQIPtr spBody2(spBody);

//  3. 创建并得到IHTMLControlRange接口
CComPtr < IDispatch >  pdispCtrlRange;   
spBody2
-> createControlRange( & pdispCtrlRange);
CComQIPtr
< IHTMLControlRange >  pCtrlRange(pdispCtrlRange);

//  4. 创建图片元素对应的IID_IHTMLControlElement的接口
IHTMLControlElement *  pCtrlElement  =  NULL;    
spElem
-> QueryInterface(IID_IHTMLControlElement, ( void ** & pCtrlElement);
或者
CComQIPtr
< IHTMLControlElement >  pCtrlElement(spElem);  //  就不naming为spCtrlElement了。保存前后一致。

//  5. Copy 到剪切板
VARIANT_BOOL vbReturn;
VARIANT vEmpty;
VariantInit(
& vEmpty);
HRESULT hAdd 
=  pCtrlRange -> add(pCtrlElement);
HRESULT hCpy 
=  pCtrlRange -> execCommand(CComBSTR(L " Copy " ), VARIANT_FALSE, vEmpty,  & vbReturn);

//  6. 这句可能不好使...
spWebBrowser2 -> ExecWB(OLECMDID_SAVECOPYAS, OLECMDEXECOPT_PROMPTUSER, NULL, NULL);
复制代码


5. IE控件拖放文件

预备

 

1. spWebBrowser2->put_RegisterAsDropTarget(VARIANT_FALSE or VARIANT_TRUE); 可以控制IE控件是否支持拖拽。

2. 貌似IE控件本身是支持拖拽文件的,所以你再去这样做,是没有用的。

 

spWebBrowser2 -> put_RegisterAsDropTarget(VARIANT_FALSE);
HWND hIEWnd 
=  FindIEServerWnd(hWnd);         //  ASSERT hIEWnd's classname == L"Internet Explorer_Server"
HRESULT hr  =  RegisterDragDrop(hIEWnd, (IDropTarget * ) & m_DndHandler);
spWebBrowser2
-> put_RegisterAsDropTarget(VARIANT_TRUE);

没用,我试验了。

你可以实现一个IDocHostUIHandlerDispatch 。然后用SetExternalUIHandler(&m_DocUIHandler);

里面有个重要的方法,实现可以这么写。

复制代码
     virtual  HRESULT STDMETHODCALLTYPE GetDropTarget( 
        
/*  [in]  */  IUnknown  * pDropTarget,
        
/*  [out]  */  IUnknown  ** ppDropTarget)
    {
        ATLASSERT(NULL 
!=  m_pDropTarget);
        
* ppDropTarget  =  m_pDropTarget;
        
return  S_OK ;
    }
复制代码

 

pDropTarget就需要你自己实现了IDropTarget了。OK,完事~

 

6. IE的ContextMenu

讲一下探索的过程吧,以下加重的词汇,就是搜索的关键字:

1) 最开始的时候,创建我的AxWindow,有这个属性,DOCHOSTUIFLAG_DISABLE_HELP_MENU。
并且实现了IDocHostUIHandlerDispatch接口,然后在ShowContextMenu里面做事情。

然后,我要现实我自己的菜单,而不是IE的菜单,我要正确的显示“拷贝”, “粘贴”, “剪切”等Menu Items。

这部分最早我是采用如下代码来判断的,不准确:

复制代码
static  BOOL CanShowPaste(IWebBrowser2  *  pWebBrowser2)
{
    CComPtr
< IDispatch >   spDisp ;
    pWebBrowser2
-> get_Document( & spDisp) ;
    
if  ( ! spDisp)
        
return  FALSE ;
    
    CComQIPtr
< IHTMLDocument2 >   spDoc2 (spDisp) ;
    
if  ( ! spDoc2)
    { ATLASSERT(FALSE); 
return  FALSE; }
    CComPtr
< IHTMLWindow2 >    spWindow2 ;
    CComPtr
< IHTMLEventObj >    spEvent ;

    spDoc2
-> get_parentWindow ( & spWindow2) ;
    
if  (spWindow2)
    {
        spWindow2
-> get_event ( & spEvent) ;
        
if  (spEvent)
        {
            CComPtr
< IHTMLElement >    spElem ;
            spEvent
-> get_srcElement ( & spElem) ;

            CComPtr
< IHTMLSelectionObject >  selObj;
            spDoc2
-> get_selection( & selObj);
            
            CComBSTR type;
            selObj
-> get_type( & type);
            
            CComBSTR  bstrTagName ;
            spElem
-> get_tagName( & bstrTagName) ;
            CString  strTag 
=  bstrTagName ;
            strTag.MakeLower() ;
            
if  ((strTag.Find(L " textarea " !=   - 1 ||  strTag.Find(L " input " !=   - 1 )
            {
                
if  (IsClipboardFormatAvailable(CF_TEXT))
                    
return  TRUE ;
            }
        }
    }

    
return  FALSE ;
}
复制代码

Paste还好,但是Copy就很难弄了。因为当你选中的东西不是单纯的text的时候,接口返回的内容,我是弄不清楚。

2) 于是,我问了以下做浏览器的同事,他们没有用自己的菜单。而是自绘IE的菜单,于是,我得到了如下代码,去获得IE菜单的句柄HMENU。

复制代码
    HMODULE hShDoclc  =  LoadLibrary(L " shdoclc.dll " );
    
if ( NULL  ==  hShDoclc )
        hShDoclc 
=  LoadLibrary(L " ieframe.dll " );

    
if  (hShDoclc  ==  NULL)
    {
        
//  Error loading module -- fail as securely as possible
         return  FALSE;
    }

    
const   int  IDR_BROWSE_CONTEXT_MENU  =   24641 ;   //###
    HMENU hMainMenu 
=  LoadMenu(hShDoclc, MAKEINTRESOURCE(IDR_BROWSE_CONTEXT_MENU));
    
if  (hMainMenu) { ...
复制代码

这样,我可以用ShowContextMenu的参数wID,它是SubMenu的ID。

但是还是有问题,似乎在这个函数中,GetMenuState,GetMenuItemInfo,都是获取不到那个即将弹出的Menu的Item的State。【原因不得而知】

后来我去掉了属性DOCHOSTUIFLAG_DISABLE_HELP_MENU,似乎也是不行的。

最后我用Detour了,Hook这个API,TrackPopupMenuEx【至少在IE8上面是OK的,我只担心IE6的内核。】。

这个API的第一个参数,就是前面提到过的SubMenu,【现在也不需要从IE的context menu中GetSubMenu了。】

从被Hook API的上下文中,居然可以得到Menu Item的准确State。 

最后看这几篇文章:

 

7. 从内存中加载HTML

 

http://blog.csdn.net/jiangsheng/archive/2003/11/09/3790.aspx

简直找到了源头。 

http://blog.csdn.net/jiangsheng/archive/2003/11/09/3790.aspx 

简介:

定位到 about:blank 

DHTML 对象模型的有效性

使用 QueryInterface 获得IPersist*等接口

使用IPersist*接口载入和保存HTML内容

载入和保存HTML元素数据

---此外还要配合以下代码才行:
复制代码
HRESULT CMyHtmlView::LoadWebPage()
{
    Navigate2(L
" about:blank " );
    HRSRC hWebPageRes 
=  FindResource(NULL, (LPCWSTR)IDR_HTML1, RT_HTML);
    DWORD dwSize 
=  SizeofResource(NULL, hWebPageRes);
    HGLOBAL hGlobal 
=  LoadResource(NULL, hWebPageRes);
    
if (hGlobal  !=  NULL)
    {
        IStream
*  pStream  =  NULL;

        LPVOID pResContent 
=  LockResource(hGlobal);
        
if  (NULL  !=  pResContent)
        {
            HGLOBAL hWebPage 
=  GlobalAlloc(GMEM_MOVEABLE, dwSize);
            LPVOID lpBuffer 
=  GlobalLock(hWebPage);
            ZeroMemory(lpBuffer, dwSize);
            CopyMemory(lpBuffer, pResContent, dwSize);
            GlobalUnlock(hWebPage);
            CreateStreamOnHGlobal(hWebPage, TRUE, 
& pStream);
        }
        _LoadHtmlFromStream(NULL, pStream);

        
return  S_OK;
    }
... ...

复制代码


8. C++调用JavaScript,支持匿名函数

_com_dispatch_method

它描述参数列表的类型的字段太不友好了。

 

你可能感兴趣的:(IE编程)