很多应用程序内嵌IE来加载网页,使用MFC的CWebBrowser2自动生成的控件,自动生成的IE控件的代码基本不可读,函数调用都是这种:
void GoBack()
{
InvokeHelper(0x64, DISPATCH_METHOD, VT_EMPTY, NULL, NULL);
}
void GoForward()
{
InvokeHelper(0x65, DISPATCH_METHOD, VT_EMPTY, NULL, NULL);
}
void GoHome()
{
InvokeHelper(0x66, DISPATCH_METHOD, VT_EMPTY, NULL, NULL);
}
void GoSearch()
{
InvokeHelper(0x67, DISPATCH_METHOD, VT_EMPTY, NULL, NULL);
}
void Navigate(LPCTSTR URL, VARIANT * Flags, VARIANT * TargetFrameName, VARIANT * PostData, VARIANT * Headers)
{
static BYTE parms[] = VTS_BSTR VTS_PVARIANT VTS_PVARIANT VTS_PVARIANT VTS_PVARIANT ;
InvokeHelper(0x68, DISPATCH_METHOD, VT_EMPTY, NULL, parms, URL, Flags, TargetFrameName, PostData, Headers);
}
当需要修改事件方式时,看MSDN上,有一套极其恶心的API,通过queryInterface来获取IDispatch对象,然后修改事件:
class MyWebEventDispatcher : public DWebBrowserEvents2 {
public:
DWORD dwCookie;
STDMETHODIMP QueryInterface(REFIID riid, void **ppvObject)
{
HRESULT hr = E_NOINTERFACE;
if (riid == __uuidof(IDispatch))
{
*ppvObject = (IDispatch*)this;
AddRef();
hr = S_OK;
}
else if (riid == __uuidof(DWebBrowserEvents2))
{
*ppvObject = (DWebBrowserEvents2*)this;
AddRef();
hr = S_OK;
}
return hr;
}
STDMETHODIMP_(ULONG) AddRef(void)
{
return 1;
}
STDMETHODIMP_(ULONG) Release(void)
{
return 1;
}
STDMETHOD(GetTypeInfoCount)(UINT*)
{
return E_NOTIMPL;
}
STDMETHOD(GetTypeInfo)(UINT, LCID, ITypeInfo**)
{
return E_NOTIMPL;
}
STDMETHOD(GetIDsOfNames)(REFIID, LPOLESTR *rgszNames, UINT, LCID, DISPID *rgDispId)
{
return E_NOTIMPL;
}
STDMETHODIMP Invoke(DISPID dispidMember,
REFIID riid, LCID lcid,
WORD wFlags,
DISPPARAMS* dispParams,
VARIANT* pvarResult,
EXCEPINFO* pExcepInfo,
UINT* puArgErr)
{
CComBSTR url;
switch (dispidMember) {
case DISPID_BEFORENAVIGATE2:
url = ((*dispParams).rgvarg)[5].pvarVal->bstrVal;
//MessageBox(NULL, L"打开窗口", NULL, NULL);
if (url == "http://www.adatum.com")
{
// If so, navigate the browser frame to standard resource page
CComQIPtr spBrowser = ((*dispParams).rgvarg)[6].pdispVal;
if (spBrowser != NULL)
{
static const CComBSTR newURL = L"res://ieframe.dll/navcancl.htm";
spBrowser->Navigate(newURL, NULL, NULL, NULL, NULL);
// Set Cancel parameter to TRUE to cancel the current event
*(((*dispParams).rgvarg)[0].pboolVal) = TRUE;
}
}
return S_OK;
case DISPID_NAVIGATECOMPLETE2:
return S_OK;
}
return S_FALSE;
}
STDMETHODIMP Advise(IUnknown* pUnkCP, const IID& iid)
{
if (pUnkCP == NULL) {
return E_INVALIDARG;
}
IConnectionPointContainer* pCPC = NULL;
HRESULT hr = pUnkCP->QueryInterface(IID_IConnectionPointContainer, reinterpret_cast(&pCPC));
if (SUCCEEDED(hr)) {
IConnectionPoint* pCP = NULL;
hr = pCPC->FindConnectionPoint(iid, &pCP);
if (SUCCEEDED(hr)) {
hr = pCP->Advise(this, &dwCookie);
pCP->Release();
}
pCPC->Release();
}
return hr;
}
/**
* Register DWebBrowserEvents2 to IWebBrowser2.
*/
STDMETHODIMP Advise(HWND hwnd, IWebBrowser2* pIE)
{
HRESULT hr = Advise(pIE, __uuidof(DWebBrowserEvents2));
return hr;
}
HRESULT connectToBrowser(IWebBrowser2* pIE) {
HRESULT hr;
CComPtr spCP;
IConnectionPointContainer* pCPC = NULL;
pIE->QueryInterface(IID_IConnectionPointContainer, reinterpret_cast(&pCPC));
// Receives the connection point for WebBrowser events
hr = pCPC->FindConnectionPoint(DIID_DWebBrowserEvents2, &spCP);
if (FAILED(hr))
return hr;
// Pass our event handlers to the container. Each time an event occurs
// the container will invoke the functions of the IDispatch interface
// we implemented.
hr = spCP->Advise(reinterpret_cast(this), &dwCookie);
return hr;
}
};
就会自动注册事件到选定的窗口,完全不用手动去修改那一堆恶心的代码:
BEGIN_EVENTSINK_MAP(CMyIETestDlg, CDialog)
ON_EVENT(CMyIETestDlg, IDC_EXPLORER1, 250, CMyIETestDlg::MyBeforeNavigate2Explorer, VTS_DISPATCH VTS_PVARIANT VTS_PVARIANT VTS_PVARIANT VTS_PVARIANT VTS_PVARIANT VTS_PBOOL)
ON_EVENT(CMyIETestDlg, IDC_EXPLORER1, 273, CMyIETestDlg::NewWindow3Explorer1, VTS_PDISPATCH VTS_PBOOL VTS_UI4 VTS_BSTR VTS_BSTR)
END_EVENTSINK_MAP()
void CMyIETestDlg::MyBeforeNavigate2Explorer(LPDISPATCH pDisp, VARIANT* URL, VARIANT* Flags, VARIANT* TargetFrameName, VARIANT* PostData, VARIANT* Headers, BOOL* Cancel)
{
// TODO: 在此处添加消息处理程序代码
CString str = URL->bstrVal;
//TRACE(str);
//MessageBox(NULL, L"asdasd", NULL, NULL);
if (str != L"http://localhost:8000/test.html") {
*Cancel = true;
ShellExecute(0, L"open", str, NULL, NULL, SW_SHOWNORMAL);
}
}
void CMyIETestDlg::NewWindow3Explorer1(LPDISPATCH* ppDisp, BOOL* Cancel, unsigned long dwFlags, LPCTSTR bstrUrlContext, LPCTSTR bstrUrl)
{
// TODO: 在此处添加消息处理程序代码
CString url = bstrUrl;
ShellExecute(0, L"open", url, NULL, NULL, SW_SHOWNORMAL);
*Cancel = TRUE;
}
IE控件加载的网页中的链接默认用IE打开,现在我需要修改为使用用户的默认浏览器打开,读了MSDN发现需要去截获BeforeNavigate2 事件,比起用QueryInterface重载IDispach:Invoke这种恶心写法,直接无脑右键添加事件方便的多。但是发现
BeforeNavigate2在新窗口打开的时候并没有被调用,只是第一次Navgate2加载链接的时候被调用了。对于新窗口打开链接需要截获NewWindow3事件,获取用户点击的链接,用ShellExecute来打开链接。
NewWindow3:读MSDN,发现窗口打开方式为_blank和windows.open时,是打开新窗口会触发这个事件。
或者用兼容性更好的NewWindow2事件:
void CMyIETestDlg::NewWindow2Explorer1(LPDISPATCH* ppDisp, BOOL* Cancel)
{
*Cancel = TRUE;
HRESULT hr;
IDispatch *pDisp = CWebBrowser2.get_Document();
IHTMLDocument2 *pHTMLDocument2 = NULL;
if (pDisp)
{
hr = pDisp->QueryInterface(IID_IHTMLDocument2, (void**)&pHTMLDocument2);
if (!SUCCEEDED(hr))
{
return;
}
}
if (pHTMLDocument2 != NULL)
{
CComPtr pIHTMLElement;
pHTMLDocument2->get_activeElement(&pIHTMLElement);
if (pIHTMLElement != NULL)
{
variant_t url;
hr = pIHTMLElement->getAttribute(L"href", 0, &url);
if (SUCCEEDED(hr))
{
CString strURL(V_BSTR(&url));
//打开默认浏览器
ShellExecute(m_hWndOwner, NULL, strURL, NULL, NULL, SW_NORMAL);
}
}
}
}