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.htmlhttp://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
简介:
---此外还要配合以下代码才行: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
它描述参数列表的类型的字段太不友好了。