June 1998
Keeping an Eye on Your Browser by Monitoring Internet Explorer 4.0 Events
Download Jun98IEEventsCode.exe (38KB)
Scott Roberts is an engineer on the Developer Support Internet Client team at Microsoft. He assists developers who are using the Internet Client SDK to create applications for Internet Explorer. Scott can be reached at ScottRobe@HotMail.com.
|
Since the introduction
of the WebBrowser control in Microsoft® Internet Explorer 3.0, creating Internet-aware applications has become fairly simple. All you have to do to incorporate Internet browsing into your application is host the WebBrowser control. With this control, you can navigate to any URL, go back or forward in the history list, or navigate directly to the user's home page. You can do most of the things that the Internet Explorer 4.0 browser application can do. Through the WebBrowser control you can also control Internet Explorer 4.0 using automation.
Being able to control Internet Explorer 4.0 is great, but if you can't tell what Internet Explorer 4.0 is doing, you still don't have full control. For this reason, Internet Explorer 4.0 exposes an event interface through which you can monitor its activity and perform certain actions. Let's say you are creating an application for use on your corporate intranet. You may want to restrict your users from navigating to a URL outside of your corporate domain. By listening to events from Internet Explorer, your app can determine when a user is navigating to an external URL and then cancel this navigation completely. To implement this, you will need to know what kind of events are fired by Internet Explorer 4.0, how you can receive events in your application, and how to implement event handling in your applications.
What are Events?
|
Figure 1 Connectable Object and its Client |
IConnectionPointContainer
IConnectionPoint
Sinking Events
|
|
Make sure you set a reference to Microsoft Internet Controls or shdocvw.dll in your project. If you don't do this, Visual Basic will not be able to resolve the reference to Internet Explorer.
Next, create an instance of the server by using the New keyword or the CreateObject method like this |
|
or like this |
|
This code will launch a new instance of Internet Explorer. This code is not necessary when hosting the WebBrowser control on a Visual Basic form. In that case, Visual Basic will take care of setting up the event sink code for you.
When you create an instance of the server in one of the two ways listed above, Visual Basic initializes and manages the event sink for you automatically. It's as simple as that. You don't have to worry about getting a pointer to the connection point container and then finding the connection point that you want. Visual Basic does this for you. After you enter the code to create the server, you then insert method calls that correspond to the events that the server is firing. You can determine what events are supported by an object by looking in the Procedures/Events box. For example, if I want to handle the DownloadBegin event fired by Internet Explorer 4.0, I would declare a method like this: |
|
When you no longer want to receive events from the server, just set your variable to Nothing like this: |
|
This brings me to the first sample that I created to demonstrate Internet Explorer 4.0 event sinkingVBIEEvtSpy (see Figure 2). This sample has one form that contains a multiline text box and two buttons. One of the buttons starts an instance of Internet Explorer 4.0. the Other exits from the program and shuts down the running instance of Internet Explorer, if any. Each time Internet Explorer 4.0 fires an event, a description of the event is placed in the text box. Coding this is straightforward. First, a variable is dimensioned, using the WithEvents keyword, to represent an instance of Internet Explorer 4.0. Then you can create event handler functions for all the events you want to know about. This sample only handles a subset of events. You are free to add event handler functions for any other event that you want.
Sinking Events in C++
the Se steps may not be apparent if you are developing in Visual Basic or using MFC or ATL, but they are very hard to ignore when creating client applications in C++. Figure 3 shows C++ code that allows you to sink events from Internet Explorer 4.0. The ConnectEvents method is called when you want to establish the event sink. The Exit method is called when you exit the program. Also, the class CSomeClass inherits from IDispatch and the m_pIE data member is an interface pointer for an instance of Internet Explorer that was created through a call to CoCreateInstance. As you may have noticed, I left out the implementation of the IDispatch::Invoke method. The server calls this method each time it fires an eventunless the client is implementing the event sink directly. Remember, though, that I'm concentrating on the IDispatch method of receiving events. The server passes to Invoke the dispatch ID (DISPID) of the event that it is firing. For Internet Explorer 4.0, the Se DISPIDs are defined in the ExDispID.h header file. To ensure you have the latest version of this file, install the Internet Client SDK. The DISPIDs for Internet Explorer 4.0 are listed in Figure 4. I have only listed those DISPIDs that pertain to Internet Explorer 4.0. DISPIDs that pertain to Internet Explorer 3.0 still exist in ExDispID.h for backward compatibility. Now let's talk about the mother of all automation methods: Invoke. Invoke takes eight arguments. For this discussion, you're only concerned with two: dispidMember and pDispParams. For information about the Other six parameters, refer to the Visual C++ documentation or Inside OLE (Microsoft Press, 1995) by Kraig Brockschmidt. The dispidMember parameter tells what event has been fired. If your client application is sinking events from Internet Explorer 4.0, the value of this parameter will correspond to one of the DISPIDs listed in Figure 4. The pDispParams input parameter is a structure that contains, among other things, the number of parameters for the event that is being fired and the actual parameters the Mselves. the Order of the parameters in this structure is from last to first. For example, Internet Explorer 4.0 fires the NavigateComplete2 event like this: NavigateComplete2(pDisp, URL). When Invoke is called, pDispParams->cArgs will contain a value of 2, with the URL in pDispParams->rgvarg[0] and pDisp in pDisp- Params->rgvarg[1]. Figure 5 shows a sample implementation of Invoke that handles the NavigateComplete2 event. This sample uses the ATL class CComVariant to convert from VARIANT to BSTR. (The ATL sample that I will discuss shortly will demonstrate more events.)
Sinking Events Using ATL
|
|
Four parameters were passed to AtlAdvise. The first is a pointer to the IUnknown of the connectable object. m_spIE is a smart pointer that represents the running instance of Internet Explorer 4.0 you are automating. Passing a smart pointer to the Advise method in this way causes the smart pointer to return a pointer to its internal object, IWebBrowser2 in this case. A pointer to IWebBrowser2 will be converted to a pointer to IUnknown when passed to a method that accepts an IUnknown pointer. This is due to the fact that IWebBrowser2, like all COM objects, inherits from IUnknown.
The second parameter must be a pointer to the IUnknown interface of the Object that represents the event sink. GetUnknown will return this pointer. Remember that to sink events for Internet Explorer 4.0, the class that represents the event sink must implement IDispatch in some way. In this case, the class is inheriting from IDispatch. The third parameter is the IID of the connection point. The IID of the connection point for Internet Explorer 4.0 events is DIID_DWebBrowserEvents2. The last parameter is a pointer to a DWORD. It stores a cookie that will be used in a call to AtlUnadvise. Everything else is the same as if this were a normal C++ application. The client must implement IDispatch::Invoke to handle the events that are fired by Internet Explorer. When you are finished receiving events from Internet Explorer, just call AtlUnadvise. For my example, the call to AtlUnadvise would look like this: |
|
This brings me to my second sample, ATLIEEvtSpy. This sample is almost exactly the same as the Visual Basic-based sample that I talked about earlier. ATLIEEvtSpy is a dialog-based application that has a list box and two buttons (see Figure 6). The Start IE4 button creates an instance of Internet Explorer 4.0 using CoCreateInstance. Next, AtlAdvise is called to establish the CIEEvtObj class as the event sink. CIEEvtObj inherits from IDispatch, and the Invoke method is implemented to handle events. Each time an event is fired by Internet Explorer 4.0, a message is displayed in the events list box. When the Exit button is pressed, the AtlUnadvise function is called to tell Internet Explorer 4.0 that you no longer want to receive events.
Sinking Events Using MFC
|
|
Next, you must specify the events that you want to handle and the functions that will be used to handle the events. To specify the Se events by their DISPID, use the DISP_FUNCTION_ID macro. This macro takes six parameters (see Figure 8). If you want to handle the DownloadComplete event, use the DISP_FUNCTION_ID macro like this: |
|
The final step is closing your dispatch map. Just use the END_DISPATCH_MAP macro. So to reiterate, the three steps to initialize your dispatch map are begin the map, specify the events, and end the map. The completed dispatch map will look like this: |
|
Sinking events when hosting a control is similar to sinking the M when you are automating a COM object. The main difference is that you don't have to Advise or Unadvise the connection point; CCmdTarget takes care of this for you. In the case of hosting a control, CCmdTarget uses an event sink map instead of a dispatch map. As you may have guessed, MFC gives you macros to define and initialize this event sink map.
Declaring the event sink map is just like declaring the dispatch map: simply specify the DECLARE_EVENTSINK_ MAP macro in your header file. In addition to declaring the map, this macro also declares functions that CCmdTarget will use to access the map. Initialize the event sink in your implementation file. You begin the event sink map with the BEGIN_EVENTSINK_MAP macro. Specify the name of your class and its base class when using this macro. Next, specify all the events you want to handle and their event handlers by using one of the ON_EVENT_XXX macros. In most cases, you will just use ON_EVENT. If you want one member function to handle a range of events, you use ON_EVENT_RANGE. the ON_EVENT macro takes five parameters, shown in Figure 9. For example, if you want to handle the Internet Explorer 4.0 DownloadComplete event, you would specify the following for the ON_EVENT macro: |
|
Finally, you must close the event sink map with the END_EVENTSINK_MAP macro. Therefore, your complete definition of the event sink map would look like this: |
|
Please note that if you are creating a dialog-based application and have placed the WebBrowser control on your dialog, you can use ClassWizard to create your event sink map and map your event handler functions.
That brings me to the final event sink sample, MFCIEEvtSpy (see Figure 10). This dialog-based application hosts the WebBrowser control and automates Internet Explorer 4.0. The sample contains two list boxes. The left-most list box displays events for an instance of Internet Explorer 4.0 that is being automated. The right list box displays events for the WebBrowser control that is being hosted in this dialog. Pressing the Start IE4 button creates an instance of Internet Explorer 4.0. CoCreateInstance is called with a CLSID of CLSID_InternetExplorer (defined in ExDisp.h) to create this instance of Internet Explorer. In addition, AfxConnectionAdvise is called to tell the DWebBrowserEvents2 connection point that it should use the CIE4Events class for the event sink for the Internet Explorer 4.0 instance you are automating. The CIE4Events class inherits from CCmdTarget and contains the event handlers for events that are fired from the running instance of Internet Explorer 4.0. Events that are fired from the WebBrowser control hosted in the dialog are handled in the CMFCIEEvtSpyDlg class.
What Events Does Internet Explorer 4.0 Fire?
BeforeNavigate2
|
|
Note that you must check the type of pDisp to make sure it is equal to WebBrowser. This is necessary because the BeforeNavigate2 event is fired for all frames that may exist on a Web page. When a page contains frames, pDisp may not be a WebBrowser object and, therefore, any navigation attempt will cause an error.
Be careful that you don't allow your code to cause an infinite loop. Remember that BeforeNavigate2 is fired each time navigation occurs. In the previous code, calling Navigate only if the Headers parameter is empty prevents an infinite loop. If the Headers parameter is empty, Navigate is called with a nonempty Headers parameter. The next time BeforeNavigate2 is fired, the Headers parameter will not be empty, so you will not call Navigate again. You must call Stop for pDisp. If you don't, the about:NavigationCanceled Web page will be displayed after the navigation is first canceled. CommandStateChange Fired when Internet Explorer 4.0 wants to notify your application that the enabled state of a WebBrowser command has changed. The input parameters for this event warrant special consideration. Command is the identifier of the command whose enabled state has changed. Currently only two commands are defined that are important for handling CommandStateChange: CSC_NAVIGATEFORWARD and CSC_ NAVIGATEBACK. the Se commands are defined in ExDisp.h. CSC_NAVIGATEFORWARD is the command for the Forward button, and CSC_NAVIGATEBACK is the command that identifies the Back button. Each time a navigation occurs, this event is fired to tell you whether or not the Forward or Back button should be enabled. The Enable parameter is TRUE if the command is enabled, or FALSE if it is disabled. For example, if there are no Web pages after the current one, the Command parameter will be CSC_NAVIGATEFORWARD and the Enable parameter will be FALSE. For more information about the CommandStateChange event, see the Knowledge Base article Q163282, "HOWTO: Using Forward & Back Buttons for WebBrowser Control." DocumentComplete Fired when a document is completely loaded. The document object can be used only after this event is fired. The document object in this case is the IHTMLDocument2 object. The fact that the document is ready to be used means that it has reached the READYSTATE_COMPLETE state. In the case of a page with no frames, DocumentComplete is fired once after everything is done. With multiple frames, DocumentComplete is fired multiple times. Not every frame will fire this event, but each frame that fires a DownloadBegin event will fire a corresponding DocumentComplete event. The DocumentComplete event has a pointer to the IDispatch parameter, which is the IDispatch of the frame for which DocumentComplete is fired. The top-level frame fires DocumentComplete in the end. Therefore, to check if a page is finished downloading, you need to check if the IDispatch* parameter is the same as the IDispatch of the WebBrowser control. For more information about determining when a Web page is finished loading using the DocumentComplete event, please see Knowledge Base article Q180366, "HOWTO: Determine When a Page is Done Loading in WebBrowser Ctrl." DownloadBegin Notifies an application that a navigation operation is beginning. This event is fired shortly after the BeforeNavigate2 event unless the navigation is canceled. Any animation or "busy" indication that the container needs to display should be connected to this event. Each DownloadBegin event will have a corresponding DownloadComplete event. DownloadComplete Occurs when a navigation operation finishes, is halted, or fails. Unlike NavigateComplete2, which is fired only when a URL is navigated to successfully, this event is always fired after navigation starts. NavigateComplete2 Fires after navigation to a hyperlink is completed on either a window or frameset element. This event replaces the Internet Explorer 3.0 events NavigateComplete and FrameNavigateComplete. NewWindow2 Occurs when a new window is to be created for displaying a resource. This event precedes the creation of a new window from within the WebBrowser object (for example, in response to navigation targeted to a new window, or to a scripted window.open method). This event is also fired when navigation occurs via the Navigate or Navigate2 methods with the navOpenInNewWindow flag. It replaces the Internet Explorer 3.0 NewWindow and FrameNewWindow events. Since this event is sometimes hard to use, I will talk about how to use both of its parameters. ppDisp is the address of an interface pointer that, optionally, receives the IDispatch interface pointer of a new WebBrowser or InternetExplorer object. This means that you can create a new instance of Internet Explorer 4.0 that you can control from within your navigation. This instance is a new, hidden, non-navigated WebBrowser or InternetExplorer object. After returning from your NewWindow2 event handler, the Object that fired this event (Internet Explorer) will then configure the new object and navigate to the target location. The Cancel parameter contains the address of a cancel flag. An application can set this parameter to TRUE to cancel the navigation operation, or to FALSE to allow it to proceed. The documentation installed with the Internet Client SDK says that setting Cancel to TRUE prevents a new window from being created, and the navigation is attempted on the current window. This is incorrect. Setting Cancel to TRUE cancels the new window creation and the navigation. This is an error in the documentation that has been fixed in the Online version. If you do nothing in your event handler for NewWindow2, a new InternetExplorer object will be created to handle the navigation. For example, you may want to handle this event so that you may control the creation of the new InternetExplorer object. Maybe you want to limit the number of instances of Internet Explorer, or perhaps you want to handle events from any new instance of Internet Explorer. The following code handles the NewWindow2 event, creates a new, hidden, non-navigated instance of the InternetExplorer object, and sets ppDisp equal to the new instance. You can add the necessary code for event sinking if you want. |
|
Another reason to handle this event is that you may want to have your application used for the new window when the user chooses to open a URL in it, as shown in this Visual Basic code: |
|
In an MFC dialog-based application, the code for NewWindow2 would look like this: |
|
You have to remember to delete m_dlgNewWB. Also, do not perform navigation in the CMyDlg::OnInitDialog method. If you do, in most cases this code will not work. Remember that ppDisp must point to a new, hidden, non-navigated WebBrowser or InternetExplorer object.
OnFullScreen Occurs when the FullScreen property has changed. OnMenuBar Occurs when the MenuBar property is changed. OnQuit Notifies an application that the Internet Explorer 4.0 application is ready to quit. You usually want to call Unadvise in the handler for this event to stop the processing of events. OnStatusBar Occurs when the StatusBar property is changed. This means that the status bar was either hidden or made visible. Onthe AterMode Occurs when the the AterMode property is changed. OnToolBar Occurs when the ToolBar property is changed. This means that the toolbar was either hidden or made visible. OnVisible Occurs when the window for the WebBrowser should be shown or hidden. This allows the host window to behave the same way the Internet Explorer 4.0 window would. ProgressChange Notifies an application that the progress of a download operation has been updated. This event has two input parameters. Progress contains the amount of total progress to show, or -1 when progress is complete. ProgressMax is the maximum progress value. The container can use the information provided by this event to display the number of bytes downloaded so far or to update a progress indicator. To calculate the percentage of progress to show, multiply the value of Progress by 100 and divide by the value of ProgressMax. If Progress is -1, the container can indicate that the Operation is finished or it can hide the progress indicator.
Additional Resources
|
From the June 1998 issue of Microsoft Systems Journal.