心寄笔端 附庸风雅

甘草的技术博客

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::

1.

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<img>
spElem->get_tagName(&bstrTagName);
// TODO: bstrTagName == img ?

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

做完一半的工作了。

4.2 拷贝到剪切板

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

// 2. "转"为IHTMLElement2接口,COM就是麻烦
CComPtr<IHTMLElement2> spBody2;
spBody
->QueryInterface(IID_IHTMLElement2, (void**)&spBody2); // CComQIPtr<IHTMLElement2> 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 <img>到剪切板
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 

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

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

 

 

posted on 2010-09-29 20:00  甘草  阅读(2326)  评论(5编辑  收藏  举报
Baidu
Google
心寄笔端
TEST
以后我会加上Power By的,先别介意