This article may contain URLs that were valid when originally published, but now link to sites or pages that no longer exist. To maintain the flow of the article, we've left these URLs in the text, but disabled the links.


June 1998

Microsoft Systems Journal Homepage

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?
      Whenever a COM object wants to notify its clients that something has happened, it sends out a message. This message is called an event and the process of sending the message is referred to as event firing.
      If an event is fired and nobody is listening, did the event ever occur? Obviously, the client application that is controlling the COM object has to be listening for the Se events. When a client application wishes to receive events from a COM object, it "advises" the COM object of this fact.
      For a COM object to talk to its client, the Object must support one or more outgoing interfaces. the Se outgoing interfaces are implemented by the client and plugged into the COM object through connection points. The part of the client that implements the Outgoing interfaces is known as an event sink. A COM object that supports outgoing interfaces is called a connectable object. To qualify as a connectable object and support connection points, the Object must implement the IConnectionPointContainer interface. It is through this interface that clients can learn which outgoing interfaces the server supports.

Figure 1  Connectable Object and its Client
Figure 1  Connectable Object and its Client


      A separate connection point represents each of the Outgoing interfaces supported by the server. Each connection point is represented by the IConnectionPoint interface. Figure 1 depicts the relationship between the connectable object and its sink. As mentioned, there are two interfaces that the server must implement in order for a client to receive events: IConnectionPointContainer and IConnectionPoint.

IConnectionPointContainer
      Every connectable object implements this interface. A client that wishes to receive events can find out about the various connection points that the connectable object supports through IConnectionPointContainer. A client can get a pointer to IConnectionPointContainer simply by calling QueryInterface using a pointer to another interface. Then the client can use one of this interface's two methods to get a pointer to a connection point. Using EnumConnectionPoints, the client can get a list of pointers to all the connection points that are supported by the connectable object.
      The client can ask the connectable object if it supports a particular interface with the Other method, FindConnectionPoint. The client specifies the interface identifier (IID) of the connection point it wants. If the connectable object supports this interface, it will return a pointer to the IConnectionPoint interface for the appropriate connection point.

IConnectionPoint
      Once the client knows which connection points the connectable object supports, it can establish a connection with it. The client advises the connectable object of the event sink that the Object should use for all events. It also lets the Object know when the client no longer wishes to receive events from the connectable object. A client can connect to the Object through the methods of the IConnectionPoint interface that I'll discuss next. Most of the time, you will only be concerned with the first two.
      The client establishes a connection with a certain connection point in the connectable object through the Advise method. Basically, the client is telling the Object, "Here is the event sink that I have implemented for this connection point. Please send all events to this sink." The client must pass a pointer to the IUnknown interface of its event sink. The connectable object uses this IUnknown pointer to query the client for the event sink interface that the connection point supports. In addition, the event sink must implement IDispatch or the event sink interface itself to receive events. (Since you typically will use the IDispatch method for sinking events in Internet Explorer 4.0, I will concentrate only on the IDispatch method of receiving events.)
      The connectable object will call the Invoke method of the IDispatch implementation each time the Object fires an event. (If you implement the event sink interface itself, the connectable object can call the event sink methods directly.) This method returns a cookie that the client must use when breaking the connection through a call to Unadvise. Speaking of the Unadvise method, when the client wants to break the connection with the connectable object, it calls this method and passes the cookie that it received from the call to Advise.
      As for the Other methods, GetConnectionPointContainer retrieves the IConnectionPointContainer interface pointer for the connectable object of the connection point. GetConnectionInterface returns the IID of the Outgoing interface managed by this connection point. This method lets the client translate from an IConnectionPoint interface pointer to an IID. This IID identifies the exact interface to which the IConnectionPoint interface points.

Sinking Events
      There are different ways to sink events depending on the development tools you are using to create your client application. Obviously, sinking events in an application created using Visual Basic® will be much different (not to mention much easier) than sinking events in a C++ application. There are even different ways to sink events using the Active Template Library (ATL), MFC, or plain vanilla C++.
      Visual Basic is the easiest development tool for creating most types of applications, so it should come as no surprise that it is the easiest to use when sinking events. I have created three samples that I will be discussing shortly. The ATL and Visual Basic-based samples look and act the same. The ATL sample took me about four hours to complete, whereas the Visual Basic sample took about 20 minutes. Don't get me wrong; I am an enthusiastic advocate of C++, ATL, and MFC, especially when I am creating my own interfaces. Visual Basic is simply the easiest development tool to use when creating client applications that sink events from servers such as Internet Explorer 4.0.
      Now that I am finished with my little lecture about Visual Basic, let's talk about how to sink events in Visual Basic. When automating a server in Visual Basic, you must first declare a variable of the type of server you are automating. When you declare this variable, you specify the WithEvents keyword to tell Visual Basic that you want to receive events from the server. For example, if you were automating Internet Explorer 4.0, you would declare a variable like so:


 Dim WithEvents IE As InternetExplorer
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

 Set IE = New InternetExplorer
or like this

 Set IE = CreateObject("InternetExplorer.Application.1" )
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:

 Private Sub IE_DownloadBegin()
     ' Insert your excellent Visual Basic code
 End Sub
When you no longer want to receive events from the server, just set your variable to Nothing like this:

 Set IE = Nothing
      This brings me to the first sample that I created to demonstrate Internet Explorer 4.0 event sinking—VBIEEvtSpy (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++
      Sinking events in a C++ application involves a little more work than it does in a Visual Basic-based application, but there are only five steps involved.

  1. Get a pointer to the connection point container (IConnectionPointContainer).
  2. Call the FindConnectionPoint method to find the connection point that you want. For Internet Explorer 4.0 you want the DWebBrowserEvents2 connection point interface. (Optionally, you can call EnumConnectionPoints to enumerate through all the connection points that the server supports.)
  3. Advise the connection point that you want to receive events. Pass a pointer to the IUnknown interface of the event sink. Remember that the connectable object will use the IUnknown pointer to query the client for the event sink interface. Most connectable objects will return a failure code from Advise if the client does not support the event sink interface. In the case of Internet Explorer 4.0, if the client does not support the event sink interface, Internet Explorer 4.0 will then query the client for IDispatch.
  4. Implement IDispatch::Invoke to handle any events that are fired.
  5. When you no longer want to receive events, you can call Unadvise and pass it the cookie that you got from the call to Advise.

      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 event—unless 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
      ATL is a library of template classes that you can use to create COM-based applications in C++. ATL makes it easy for you to create ActiveX® controls, and provides default implementations of certain COM interfaces. ATL also gives you two functions that make it easy to sink events for any connectable object: AtlAdvise and AtlUnadvise.
      AtlAdvise tells a connectable object that you want to receive events from it. This function takes care of the first three steps of event sinking outlined previously, saving you a lot of time and effort. Just like the IConnectionPoint:: Advise method, AtlAdvise returns a cookie that will be used in a call to AtlUnadvise. AtlUnadvise tells the connectable object that you no longer want to receive events.
      Say you have an ATL application that is automating Internet Explorer 4.0. To tell Internet Explorer 4.0 that you want to receive events, you would make the following call to AtlAdvise:


 HRESULT hr = AtlAdvise(m_spIE, GetUnknown(),
                        DIID_DWebBrowserEvents2,
                        &m_dwCookie);
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:

 HRESULT hr = AtlUnadvise(m_spIE,
                          DIID_DWebBrowserEvents2,
                          m_dwCookie);
      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
      MFC provides a number of handy macros that you can use when sinking events, whether you are automating a server or hosting a control. Therefore, I will talk about sinking events in MFC for both cases.
      Whether you are automating a COM object or hosting a control, remember that the class that you are going to use for the event sink must inherit either directly or indirectly from CCmdTarget. CCmdTarget implements the IDispatch interface used for sinking events. In addition, you must call EnableAutomation to initialize the IDispatch interface that is contained within CCmdTarget.
      Sinking events in MFC when automating a COM object is relatively easy. All you have to do is advise the connection point of your desire to receive events by calling AfxConnectionAdvise. When you are finished receiving events, you call AfxConnectionUnadvise. the Se two functions are defined in afxctl.h. AfxConnectionAdvise takes care of querying for the connection point container, finding the connection point, and advising the connection point. This function takes five parameters, which are listed in Figure 7.
      The input parameters for AfxConnectionUnadvise are the same, except that bRefCount indicates if the reference count of pUnkSink should be incremented. Also, the final parameter is not a pointer to the cookie; it is the value of the cookie itself.
      Handling events is just as easy as setting up the connection. Remember that for your event sink to receive events, it must inherit in some way from CCmdTarget. CCmdTarget uses a dispatch map to determine which event handler function to call when it receives an event. You must first declare this dispatch map in your header file and then initialize it in your implementation file. Fortunately, MFC gives you a set of macros that you can use to create and initialize the dispatch map.
      To create a dispatch map, simply specify the DECLARE_ DISPATCH_MAP macro in the header file for your event sink class. This macro defines the dispatch map and a few functions that CCmdTarget will use to access the map.
      To initialize your dispatch map, place a few macros in the implementation file for your event sink map. BEGIN_ DISPATCH_MAP takes the name of your event sink class and the name of its base class. For example, if the name of your event sink class is CEventSink and it inherits from CCmdTarget, the BEGIN_DISPATCH_MAP macro will look like this:


 BEGIN_DISPATCH_MAP(CEventSink, CCmdTarget)
      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:

 DISP_FUNCTION_ID(CIE4Events, "DownloadComplete",
                  DISPID_DOWNLOADCOMPLETE,
                  OnDownloadComplete,
                  VT_EMPTY, VTS_NONE)
      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:

 BEGIN_DISPATCH_MAP(CEventSink, CCmdTarget)
 DISP_FUNCTION_ID(CIE4Events, "DownloadComplete",
                  DISPID_DOWNLOADCOMPLETE,
                  OnDownloadComplete,
                  VT_EMPTY, VTS_NONE)
 END_DISPATCH_MAP()
      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:

 ON_EVENT(CMyDlg, IDC_WEBBROWSER,    
          DISPID_DOWNLOADCOMPLETE, 
          OnDownloadComplete, VTS_NONE)
      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:

 BEGIN_EVENTSINK_MAP(CMyDlg, CDialog)
 ON_EVENT(CMyDlg, IDC_WEBBROWSER, 
         DISPID_DOWNLOADCOMPLETE, 
         OnDownloadComplete, VTS_NONE)
 END_EVENTSINK_MAP()
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?
      As you already know, Internet Explorer 4.0 fires events the same way as any other COM object—through a connection point. Actually, Internet Explorer 4.0 fires events through the DWebBrowserEvents2 connection point. But why on earth would Internet Explorer 4.0 fire events? Any time that Internet Explorer 4.0 wants to provide information to its clients about the current activity, it fires an event through the DWebBrowserEvents2 connection point. OK, this seems pretty obvious, but how can you figure out what events are fired by Internet Explorer? The best source of information is the Internet Client SDK online help, available at http://msdn.microsoft.com/support/inetsdk/. Another way is by using the OLE-COM Object Viewer available with Visual C++ 5.0 or at http://www.microsoft.com/oledev.
      Let's examine the events that are fired by Internet Explorer 4.0. See Figure 11 for a listing of all the Se events in vtable order. There are special considerations for some of the Se events, and many of the M replace events that were originally fired by Internet Explorer 3.0. In the Se cases, the replaced events will be listed as well. I will also talk about different actions that you can take depending on the type of event that you are handling. For each event, I will only discuss those input/output parameters that have special considerations. To obtain a full listing of all input parameters for an event, please refer to the Internet Client SDK online help.

BeforeNavigate2
      The BeforeNavigate2 event does just what it says: it is fired before Internet Explorer 4.0 navigates to a Web page. This event is fired when the user enters a URL, presses the Back or Forward button, or any time an action is performed that causes navigation to occur. This event is also fired by the WebBrowser control if you call a navigation method of an instance of the control such as Navigate, Navigate2, GoHome, or GoSearch. This event replaces the Internet Explorer 3.0 BeforeNavigate and FrameBeforeNavigate events. The following sections describe each of its input parameters.
pDisp Address of the IDispatch interface of the top-level or frame WebBrowser object corresponding to the navigation.
URL URL to be navigated to.
Flags Reserved for future use.
TargetFrameName String that contains the name of the frame in which to display the resource, or NULL if no named frame is targeted for the resource.
PostData Address of data to send to the server if the HTTP POST transaction is being used.
Headers Additional HTTP headers to send to the server (HTTP URLs only). The headers can specify things like the action required of the server, the type of data being passed to the server, or a status code.
Cancel 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.
      If you want, you can cancel or modify the navigation in your handler function for this event. To cancel the navigation, you set the Cancel parameter to TRUE. You can modify the navigation and then navigate using the pDisp parameter.
      For instance, let's say that I want to stop the current navigation, add some header information, and then navigate to the Original URL with the added header information. In Visual Basic, you would use the following code:


 Private Sub WebBrowser1_BeforeNavigate2(ByVal pDisp As_
 Object,URL As Variant, Flags As Variant,              _
     TargetFrameName As Variant, PostData As Variant,  _
     Headers As Variant, Cancel As Boolean)            _
 
     If TypeName(pDisp) = "WebBrowser" And Headers = "" Then
 pDisp.Stop
 pDisp.Navigate URL, Flags, TargetFrameName, PostData, _
     Headers + "MyHeaders"
 Cancel = True
     End If
 End Sub
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.

 void CMyEvtSink::NewWindow2(LPDISPATCH* ppDisp, 
                            BOOL* Cancel)
 {
 // Note that m_pIE is a class member of type 
 // IWebBrowser2*
 HRESULT hr = CoCreateInstance(CLSID_InternetExplorer,
                               NULL,
                               CLSCTX_LOCAL_SERVER,
                               IID_IWebBrowser2, 
                               (void**)&m_pIE);
    if (hr == S_OK)
        *ppDisp = (IDispatch*)m_pIE;

    // Do not set Cancel to TRUE.  If you do,
    // the navigation will be canceled completely.
 }
      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:

 Private Sub WebBrowser1_NewWindow2(ppDisp As Object, _ 
                                    Cancel As Boolean)
    Dim frmWB As Form1
    Set frmWB = New Form1
 
    Set ppDisp = frmWB.WebBrowser1.Object
    frmWB.Visible = True
 
    Set frmWB = Nothing
 End Sub
In an MFC dialog-based application, the code for NewWindow2 would look like this:

 void CMyDlg::OnNewWindow2(LPDISPATCH FAR* ppDisp, 
                          BOOL FAR* Cancel) 
 {
    m_dlgNewWB = new CMyDlg;
    m_dlgNewWB->Create(IDD_MYDLG_DIALOG);
 
    *ppDisp = m_dlgNewWB->m_webBrowser.GetApplication();
 }
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
      So where should you go for help if you run into trouble when creating applications using Internet Explorer 4.0? Your first place for information, besides the Internet Client SDK online help, should be the Internet Client SDK support site, http://www.microsoft.com/windows/ie/authors/?/Windows/ie/authors/Main.htm. This site contains a link to the FAQ for the Internet Client SDK. If you don't find the information you need there, navigate to the Microsoft Product Support Services site at http://msdn.microsoft.com/support, where you can search the Knowledge Base for help with any problems you may encounter.
      Knowledge Base articles that deal with automating Internet Explorer 4.0 or hosting the WebBrowser control are listed in Figure 12. If you send email to mshelp@microsoft.com with the Knowledge Base article number in the subject line, you will receive a reply that contains the contents of the Knowledge Base article. I have also included an HTML page that contains links to each of the Se Knowledge Base articles with each of the sample code modules.

From the June 1998 issue of Microsoft Systems Journal.