MFC IE控件修改网页链接打开方式

很多应用程序内嵌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;
	}
};

其实通常不用这么麻烦,右键到IE控件上,选择“添加事件处理程序”,然后可以选择事件。

MFC IE控件修改网页链接打开方式_第1张图片
就会自动注册事件到选定的窗口,完全不用手动去修改那一堆恶心的代码:

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);
			}
		}
	}
}


你可能感兴趣的:(C++学习)