Introduction
If you have ever tried to capture an event from the Internet Explorer web browser when a user clicks the Refresh button, you will know it is not easy. Capturing Internet Explorer events is generally not easy using MFC or COM. I have created a class that I use to capture DWebBrowserEvents2
events and the logic needed to detect a page refresh. There are no events specifically sent for a page refresh and the normal events you check for do not work. So you have to use a work around. My class is called CIEComCtrlSink
. It is derived from CCmdTarget
and is used to capture DWebBrowserEvents2
events. You can also use the logic to solve this problem in JavaScript etc.
Background
I did some searching on the Internet for a solution to this Microsoft bug/issue and only found some talk of a workout in Google newsgroups. So after I got it working, I thought I better throw the solution up on CodeProject.
Using the code
In the sample application, I just used an MFC Wizard to create a new Dialog project. In the file IERefreshSampleDlg.cpp I respond to a button click and create a new web browser object and advise it we want events from it.
BOOL CIERefreshSampleDlg::CreateMyIEBrowser()
{
if(m_pMyIESink != NULL)
{
AfxMessageBox("Sorry just one browser in this sample :)");
return false;
}
m_pMyIESink = new CIEComCtrlSink();
m_pMyIESink->m_pParent = this;
BOOL bOK = m_pMyIESink->MyAdviseSink();
return bOK;
}
The CIEComCtrlSink
class does all the work. It is derived from CCmdTarget
so we can get events from DWebBrowserEvents2
object.
BOOL CIEComCtrlSink::MyAdviseSink()
{
HRESULT hr = CoCreateInstance(CLSID_InternetExplorer,
NULL,
CLSCTX_LOCAL_SERVER,
IID_IWebBrowser2,
(void**)&m_pWebBrowser2);
if(!SUCCEEDED(hr))
{
CString strMsg;
strMsg.Format("Failed to create object error = %d",(int)hr);
AfxMessageBox(strMsg);
return false;
}
hr = m_pWebBrowser2->QueryInterface(IID_IWebBrowserApp,
(void**)&m_pIEApp);
hr = m_pIEApp->put_StatusBar(true);
hr = m_pIEApp->put_ToolBar(true);
hr = m_pIEApp->put_MenuBar(true);
long hIE;
hr = m_pWebBrowser2->get_HWND(&hIE);
if(!SUCCEEDED(hr))
{
CString strMsg;
strMsg.Format("Failed to create object error = %d",(int)hr);
AfxMessageBox(strMsg);
return FALSE;
}
m_wndWebBrowser.Attach((HWND)hIE);
m_wndWebBrowser.EnableScrollBar(SB_BOTH, ESB_DISABLE_BOTH);
m_wndWebBrowser.ShowWindow(SW_NORMAL);
Navigate2("http://www.stevefoxover.com");
LPUNKNOWN pUnkSink = GetIDispatch(FALSE);
AfxConnectionAdvise((LPUNKNOWN)m_pWebBrowser2,
DIID_DWebBrowserEvents2,pUnkSink,FALSE,&m_dwCookie);
return TRUE;
}
Points of interest
The tricky part is actually working out if the refresh button was hit. The logic goes as follows:
- Each time
BeforeNavigate2()
is called we add 1 to a page counter m_nPageCounter ++;
- Each call to
DocumentComplete()
, we take 1 from the page counter m_nPageCounter --;
- Each call to
DownloadBegin()
, we check if m_nPageCounter == 0
. If so we know it's a refresh page call.
- We also add to an object counter here
m_nObjCounter ++;
- Also we set a member variable to
true
to note it's a refresh call, m_bIsRefresh = true;
- On
DownloadEnd()
we decrease the counter from DownloadBegin
. m_nObjCounter --;
- If
m_nObjCounter
is zero and we are in refresh mode we know that the refreshed page has loaded. if(m_bIsRefresh && m_nObjCounter == 0)
Clear as mud? Just compile the sample application and put break points in:
CIEComCtrlSink::BeforeNavigate2()
CIEComCtrlSink::DocumentComplete()
CIEComCtrlSink::DownloadBegin()
CIEComCtrlSink::DownloadEnd()
You will then see what is called and when from Internet Explorer. IE never calls BeforeNavigate2()
or DocumentComplete()
on a page refresh. (Bad IE...)
History
My first release...