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
使用 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
它描述参数列表的类型的字段太不友好了。