IEHelper - Internet Explorer 助手类
本文展示了如何使用 IWebBrowser2、IHTMLDocument2 和 IHTMLElement 对象。
引言
本文的目的是展示如何使用 IWebBrowser2
、IHTMLDocument2
和 IHTMLElement
对象。
创建新的网页浏览器对象
让我们从一个非常简单的例子开始:如何创建一个新的 Internet Explorer 窗口。下面的代码展示了如何做到这一点。
HRESULT hr; IWebBrowser2* pWebBrowser = NULL; hr = CoCreateInstance (CLSID_InternetExplorer, NULL, CLSCTX_SERVER, IID_IWebBrowser2, (LPVOID*)&pWebBrowser); if (SUCCEEDED (hr) && (pWebBrowser != NULL)) { m_pWebBrowser->put_Visible (VARIANT_TRUE); // OK, we created a new IE Window and made it visible // You can use pWebBrowser object to do whatever you want to do! } else { // Failed to create a new IE Window. // Check out pWebBrowser object and // if it is not NULL (should never happen), the release it! if (pWebBrowser) pWebBrowser->Release (); }
连接到正在运行的 IE 实例
创建新的 IE 窗口是一项相当容易的任务。但是,如果您想使用现有的 Internet Explorer 窗口而不是创建一个新的 IE 窗口,该怎么办?在这种情况下,任务会更加复杂。下面的函数会查找一个 Internet Explorer 窗口。sTitleToSearch
是要搜索的网页的标题。允许使用通配符。如果您想查找任何 IE 窗口,只需输入“*”作为标题。
bool CMyInternetExplorer::FindUsingTitle (const CString & sTitleToSearch) { if (m_pWebBrowser != NULL) { m_pWebBrowser->Release (); m_pWebBrowser = NULL; } HRESULT hr; SHDocVw::IShellWindowsPtr spSHWinds; hr = spSHWinds.CreateInstance (__uuidof(SHDocVw::ShellWindows)); if (FAILED (hr)) return false; ASSERT (spSHWinds != NULL); long nCount = spSHWinds->GetCount (); IDispatchPtr spDisp; for (long i = 0; i < nCount; i++) { _variant_t va (i, VT_I4); spDisp = spSHWinds->Item (va); IWebBrowser2 * pWebBrowser = NULL; hr = spDisp.QueryInterface (IID_IWebBrowser2, & pWebBrowser); if (pWebBrowser != NULL) { HRESULT hr; IDispatch* pHtmlDocDispatch = NULL; IHTMLDocument2 * pHtmlDoc = NULL; // Retrieve the document object. hr = pWebBrowser->get_Document (&pHtmlDocDispatch); if (SUCCEEDED (hr) && (pHtmlDocDispatch != NULL)) { // Query for IPersistStreamInit. hr = pHtmlDocDispatch->QueryInterface (IID_IHTMLDocument2, (void**)&pHtmlDoc); if (SUCCEEDED (hr) && (pHtmlDoc != NULL)) { CString sTitle; HWND hWnd = NULL; pWebBrowser->get_HWND ((long*)(&hWnd)); if (::IsWindow (hWnd)) { int nLen = ::GetWindowTextLength (hWnd); ::GetWindowText (hWnd, sTitle.GetBufferSetLength (nLen), nLen + 1); sTitle.ReleaseBuffer (); } // If I cannot get the window title // (should never happen though) // So, lets just use the title of the document if (sTitle.IsEmpty ()) { BSTR bstrTitle; hr = pHtmlDoc->get_title (&bstrTitle); if (!FAILED (hr)) { sTitle = bstrTitle; SysFreeString (bstrTitle); } } if (StringHelper::WildcardCompareNoCase (sTitleToSearch, sTitle)) { m_pWebBrowser = pWebBrowser; pHtmlDoc->Release (); pHtmlDocDispatch->Release (); // Exit the method safely! return true; } pHtmlDoc->Release(); } pHtmlDocDispatch->Release (); } pWebBrowser->Release (); } } return false; }
MSDN 详细介绍了这种方法。 点击这里获取更多信息。
这种方法使用 SHDocVw.ShellWindows
集合来枚举所有 shell 窗口的实例。ShellWindows
对象代表属于 shell 的打开窗口的集合。实际上,此集合包含指向 Internet Explorer 和其他属于 shell 的窗口(如 Windows Explorer)的引用。为了区分 Internet Explorer 和其他 shell 窗口,我们只需尝试获取 shell 窗口的 HTML 文档。如果成功获取文档,那么此 ShellWindow
实例实际上是一个 Internet Explorer 窗口。
导航到网页
现在,在获取网页浏览器对象并将其存储在变量 m_pWebBrowser
中之后,导航到网页就非常容易了。
void CMyInternetExplorer::Navigate(LPCTSTR lpszURL, DWORD dwFlags /* = 0 */, LPCTSTR lpszTargetFrameName /* = NULL */ , LPCTSTR lpszHeaders /* = NULL */, LPVOID lpvPostData /* = NULL */, DWORD dwPostDataLen /* = 0 */) { CString strURL (lpszURL); BSTR bstrURL = strURL.AllocSysString (); COleSafeArray vPostData; if (lpvPostData != NULL) { if (dwPostDataLen == 0) dwPostDataLen = lstrlen ((LPCTSTR) lpvPostData); vPostData.CreateOneDim (VT_UI1, dwPostDataLen, lpvPostData); } m_pWebBrowser->Navigate (bstrURL, COleVariant ((long) dwFlags, VT_I4), COleVariant (lpszTargetFrameName, VT_BSTR), vPostData, COleVariant (lpszHeaders, VT_BSTR)); SysFreeString (bstrURL); }
等待网页加载完成
使用上述函数开始加载网页后,要等待网页完全加载,我们可以使用 IWebBrowser2
对象的 READYSTATE
属性。
bool CMyInternetExplorer::WaitTillLoaded (int nTimeout) { READYSTATE result; DWORD nFirstTick = GetTickCount (); do { m_pWebBrowser->get_ReadyState (&result); if (result != READYSTATE_COMPLETE) Sleep (250); if (nTimeout > 0) { if ((GetTickCount () - nFirstTick) > nTimeout) break; } } while (result != READYSTATE_COMPLETE); if (result == READYSTATE_COMPLETE) return true; else return false; }
此函数等待网页完全加载或超时。要无限期等待,请将 nTimeout
参数设置为 0。
在网页上查找锚点
下面的函数搜索网页中指定的锚点。锚点可以通过名称、外部文本、工具提示或 URL 来指定。锚点元素的语法如下:
<a href = "anhor_URL" name ="anchor_name"
title = "anchor_tooltip">Outer Text</a>
如果 bClick
参数设置为 true
,则在找到锚点时,还会单击它。
bool CMyInternetExplorer::FindAnchor (bool bClick, bool bFocus, bool bName, bool bOuterText, bool bTooltip, bool bURL, LPCTSTR sName, LPCTSTR sOuterText, LPCTSTR sTooltip, LPCTSTR sURL) { ASSERT (m_pWebBrowser != NULL); if (m_pWebBrowser == NULL) return false; HRESULT hr; IDispatch* pHtmlDocDispatch = NULL; IHTMLDocument2 * pHtmlDoc = NULL; bool bSearch = true; // Retrieve the document object. hr = m_pWebBrowser->get_Document (&pHtmlDocDispatch); if (SUCCEEDED (hr) && (pHtmlDocDispatch != NULL)) { hr = pHtmlDocDispatch->QueryInterface (IID_IHTMLDocument2, (void**)&pHtmlDoc); if (SUCCEEDED (hr) && (pHtmlDoc != NULL)) { IHTMLElementCollection* pColl = NULL; hr = pHtmlDoc->get_all (&pColl); if (SUCCEEDED (hr) && (pColl != NULL)) { // Obtained the Anchor Collection... long nLength = 0; pColl->get_length (&nLength); for (int i = 0; i < nLength && bSearch; i++) { COleVariant vIdx ((long)i, VT_I4); IDispatch* pElemDispatch = NULL; IHTMLElement * pElem = NULL; hr = pColl->item (vIdx, vIdx, &pElemDispatch); if (SUCCEEDED (hr) && (pElemDispatch != NULL)) { hr = pElemDispatch->QueryInterface (IID_IHTMLElement, (void**)&pElem); if (SUCCEEDED (hr) && (pElem != NULL)) { BSTR bstrTagName; CString sTempTagName; if (!FAILED (pElem->get_tagName (&bstrTagName))) { sTempTagName = bstrTagName; SysFreeString (bstrTagName); } if (sTempTagName == _T ("a") || sTempTagName == _T ("A")) { IHTMLAnchorElement * pAnchor = NULL; hr = pElemDispatch->QueryInterface (IID_IHTMLAnchorElement, (void**)&pAnchor); if (SUCCEEDED (hr) && (pAnchor != NULL)) { BSTR bstrName, bstrOuterText, bstrURL, bstrTooltip; CString sTempName, sTempOuter, sTempURL, sTempTooltip; if (!FAILED (pElem->get_outerText (&bstrOuterText))) { sTempOuter = bstrOuterText; SysFreeString (bstrOuterText); } if (!FAILED (pElem->get_title (&bstrTooltip))) { sTempTooltip = bstrTooltip; SysFreeString (bstrTooltip); } if (!FAILED (pAnchor->get_name (&bstrName))) { sTempName = bstrName; SysFreeString (bstrName); } if (!FAILED (pAnchor->get_href (&bstrURL))) { sTempURL = bstrURL; SysFreeString (bstrURL); } // Do the comparison here! bool bMatches = true; if (bMatches && bName) { if (!StringHelper::WildcardCompareNoCase (sName, sTempName)) bMatches = false; } if (bMatches && bOuterText) { if (!StringHelper::WildcardCompareNoCase (sOuterText, sTempOuter)) bMatches = false; } if (bMatches && bURL) { if (!StringHelper::WildcardCompareNoCase (sURL, sTempURL)) bMatches = false; } if (bMatches && bTooltip) { if (!StringHelper::WildcardCompareNoCase (sTooltip, sTempTooltip)) bMatches = false; } if (bMatches) { // No need to search more! bSearch = false; if (bFocus) pAnchor->focus (); if (bClick) pElem->click (); } pAnchor->Release (); } } pElem->Release (); } pElemDispatch->Release (); } } pColl->Release (); } pHtmlDoc->Release(); } pHtmlDocDispatch->Release (); } if (bSearch == false) return true; return false; }
这里的想法非常简单。我们首先使用 IHTMLDocument2
的 get_all
函数枚举所有 IHTMLElement
对象。然后,我们检查所有元素是否为锚点对象 (IHTMLAnchorElement
),方法是检查其标签名。如果它是“a”或“A”,则它是一个锚点对象。然后我尝试使用 QueryInterface
函数获取 IHTMLAnchor
对象。我检查名称而不是仅仅使用 QueryInterface
函数的原因与性能有关。我猜检查标签名比尝试使用 QueryInterface
函数获取 IHTMLAnchorElement
要快得多。
填写网页表单
这里的想法与上面类似。我尝试查找所有输入元素,然后执行相同的操作,而不是查找锚点元素。input
元素的语法如下:
<input type="input_type" value="input_value" name="input_name">
bool CMyInternetExplorer::FindInput (bool bClick, bool bSelect, bool bChangeValue, bool bSetCheck, bool bType, bool bName, bool bValue, LPCTSTR sTypeToLook, LPCTSTR sNameToLook, LPCTSTR sValueToLook, bool bNewCheckValue, LPCTSTR sNewValue) { ASSERT (m_pWebBrowser != NULL); if (m_pWebBrowser == NULL) return false; HRESULT hr; IDispatch* pHtmlDocDispatch = NULL; IHTMLDocument2 * pHtmlDoc = NULL; bool bSearch = true; // Retrieve the document object. hr = m_pWebBrowser->get_Document (&pHtmlDocDispatch); if (SUCCEEDED (hr) && (pHtmlDocDispatch != NULL)) { hr = pHtmlDocDispatch->QueryInterface (IID_IHTMLDocument2, (void**)&pHtmlDoc); if (SUCCEEDED (hr) && (pHtmlDoc != NULL)) { IHTMLElementCollection* pColl = NULL; hr = pHtmlDoc->get_all (&pColl); if (SUCCEEDED (hr) && (pColl != NULL)) { // Obtained the Anchor Collection... long nLength = 0; pColl->get_length (&nLength); for (int i = 0; i < nLength && bSearch; i++) { COleVariant vIdx ((long)i, VT_I4); IDispatch* pElemDispatch = NULL; IHTMLElement * pElem = NULL; hr = pColl->item (vIdx, vIdx, &pElemDispatch); if (SUCCEEDED (hr) && (pElemDispatch != NULL)) { hr = pElemDispatch->QueryInterface (IID_IHTMLElement, (void**)&pElem); if (SUCCEEDED (hr) && (pElem != NULL)) { BSTR bstrTagName; CString sTempTagName; if (!FAILED (pElem->get_tagName (&bstrTagName))) { sTempTagName = bstrTagName; sTempTagName.MakeLower (); //AfxMessageBox (sTempTagName); SysFreeString (bstrTagName); } if (sTempTagName == _T ("input")) { IHTMLInputElement * pInputElem = NULL; hr = pElemDispatch->QueryInterface (IID_IHTMLInputElement, (void**)&pInputElem); if (SUCCEEDED (hr) && (pInputElem != NULL)) { BSTR bstrType, bstrName, bstrValue; CString sTempType, sTempName, sTempValue; if (!FAILED (pInputElem->get_type (&bstrType))) { sTempType = bstrType; SysFreeString (bstrType); } if (!FAILED (pInputElem->get_name (&bstrName))) { sTempName = bstrName; SysFreeString (bstrName); } if (!FAILED (pInputElem->get_value (&bstrValue))) { sTempValue = bstrValue; SysFreeString (bstrValue); } // Do the comparison here! bool bMatches = true; if (bMatches && bType) { if (!StringHelper::WildcardCompareNoCase (sTypeToLook, sTempType)) bMatches = false; } if (bMatches && bName) { if (!StringHelper::WildcardCompareNoCase (sNameToLook, sTempName)) bMatches = false; } if (bMatches && bValue) { if (!StringHelper::WildcardCompareNoCase (sValueToLook, sTempValue)) bMatches = false; } if (bMatches) { // No need to search more! bSearch = false; if (bSetCheck) { if (bNewCheckValue) pInputElem->put_checked (VARIANT_TRUE); else pInputElem->put_checked (VARIANT_FALSE); } if (bChangeValue) { CString sTemp (sNewValue); BSTR bstrNewValue = sTemp.AllocSysString (); pInputElem->put_value (bstrNewValue); SysFreeString (bstrNewValue); } if (bSelect) pInputElem->select (); if (bClick) pElem->click (); } pInputElem->Release (); } } pElem->Release (); } pElemDispatch->Release (); } } pColl->Release (); } pHtmlDoc->Release(); } pHtmlDocDispatch->Release (); } if (bSearch == false) return true; return false; }
您应该记住的一些要点
- 附带的源代码包含了一些额外的函数来填充网页上的表单。
- 上面的源代码没有搜索
Frame
对象。但是,这应该不是一项艰巨的任务。应该枚举框架对象,然后递归检查每个框架的IHTMLDocument2
。也许我会在下一个更新中实现这一点。 - 要使用
IHTMLInputElement
对象,您至少需要 Internet Explorer 5.0。 - 我不是 Internet Explorer 方面的专家程序员。最近,我需要自动化 Internet Explorer,然后编写了这些函数。我大部分都是自己编写的,其中可能包含一些 bug 修复。
- 请不要问我为什么使用 MFC 而不是 ATL。原因是,我不知道 ATL :( 但是,如果您有人帮助我将此代码转换为 ATL,我将非常高兴。我甚至可能开始学习 ATL :-)