Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

An MDI-Style Web Browser and Load Spy Monitor


February 2003/An MDI-Style Web Browser and Load Spy Monitor


A versatile web browser is essential for daily Internet/intranet surfers as well as web app development. In fact, multiple browser windows are often needed in web programming. You may need to compare HTML contents or test different scripts with an XML file shown simultaneously. A Multiple Document Interface (MDI) would be ideal for this task, but both the Internet Explorer and Navigator browsers are historically Single Document Interface apps. Other browsers, such as the Opera browser, are MDI apps, where multiple browser instances are available within the main window.

In this article, I'll create a similar MDI browser app, named MDIIE.EXE, shown in Figure 1. When you jump to the next site, you can set a preference of opening a new child window by toggling the Open New button. Thus, the Window menu lets you manage the child browsers as a typical MDI app. I'll also present an additional client-side widget called Load Spy that can catch the footprints of loading web stuff from images to scripts. Note that MDIIE is not a full-featured browser. The main purpose of MDIIE is to demonstrate the browser event handling. Although MDIIE is adapted from a VC++ sample, the approach can similarly apply to VB programming because it uses the same WebBrowser COM object.

The WebBrowser Control

The WebBrowser object comes from the system component SHDOCVW.DLL that can be seen in the Internet Explorer architecture (4.0 and later) in Figure 2. As an in-proc COM server, it must be hosted by a process in VC++ or VB-based apps. This big DLL (1.3 MB, v 6.0.2712.3 in Win98) supplies most browsing functionality such as navigation, in-place link, history management, and so on, and also exposes the interfaces as an ActiveX to its host (i.e., IEXPORE.EXE). The small standalone executable IEXPORE.EXE (89 KB, v 6.0.2600.0) supplies the frame, toolbar, and other UIs, and delegates tasks to the underlying components. Hence, the WebBrowser gives you the power to embed web-browsing capability into your apps. You can consult with the resource in References for more details.

The WebBrowser component provides two interfaces. The IWebBrowser2 interface enables apps to implement a WebBrowser instance with a set of properties and methods. The DWebBrowserEvents2 interface implements an event sink so that apps can receive notifications. In MFC, working with the WebBrowser control in a dialog is the same as other ActiveX components after you insert it into a template; see Figure 3. A dialog demo can be found in Aaron Skonnard's article (see References). Another alternative is the CHtmlView class that encapsulates two WebBrowser interfaces and provides the functions in the context of MFC's document/view architecture.

The Microsoft VC++ sample MFCIE uses CHtmlView to implement a subset of the IE's functionality, such as Home, Back, Print, Font, and so on. You can build MFCIE to try it like an IE browser. However, MFCIE is an SDI app with only one browser view in the main window. The Visual Basic App Wizard can generate a trivial implementation to incorporate the multiple browser views into an MDI app. Figure 4 illustrates such a program, VBMDI.EXE.

But whether in MFCIE or in VBMDI, if you want to jump to a URL that requires a new window to open (non in-place link), this site will be opened in a new IE browser window, rather than in an MFCIE window or in a VBMDI child browser. This occurs when you use "Open In New Window" in the context menu, or when the site itself needs a new browser instance. You can click, for example in Figure 4, the Mail icon in the CNN page to see where the new "Netcenter" window appears.

The NewWindow2 Event in MDI

Now, the event NewWindow2 comes to the rescue. The WebBrowser control fires NewWindow2 to the host app before a new browser instance opened. This gives you a chance to cancel the opening or to make a custom browser window. As you see in Listing 1, the NewWindow2 handler receives two output parameters. The first, ppDisp, is a pointer to the IDispatch interface for a newly created WebBrowser object. If nothing is set to it, as mentioned previously, the IE browser gets called. The second Cancel is a pointer of BOOL type with the value TRUE to cause navigation to stop. You can supply these arguments to tell the framework how to proceed.

To experiment with the event, I need an MDI app first. As MFCIE looks more like an IE browser, I started with it to revamp this SDI sample to my MDI demo. By making use of its resources, mainframe, and UI code, I just added a child frame and the Window menu with some changes to fit MDI. You can check with the MDIIE source code online for this conversion.

The article Q184876 in Knowledge Base proposes several helpful snippets in VB and VC++. However, the code in MDI needs a little modification and Listing 1 shows the correct solution. In the handler OnNewWindow2(), I first get the document template and use it to create a new document object that is required for the next creation of a new child frame. Q184876 calls View's GetDocument() to reuse the current document in a new child frame, which may work in SDI but causes the window title duplication in MDI. Next, since a child frame contains a CMfcieView object with a WebBrowser control, the view pointer pWBVw is retrieved first from this frame and then from the IDispatch address. Before returning ppDisp, you need to set TRUE to the RegisterAsBrowser property for this WebBrowser to participate in window name resolution.

Now MDIIE works fine in response to a navigation targeted to a new window or to a scripted window.open() method. So the road is clear for implementing the feature of the Open New button (Figure 1). This button can be toggled between up and down states. When it's in the down state, a site will be opened in a new child browser, and you either click an item in the Favorites, or manually enter a URL or select it in the Address combo box. For the implementation, I add the flag m_bOpenNew to the mainframe and check it in three message handlers. If m_bOpenNew is TRUE, I call the WebBrowser's method Navigate2() and pass its second parameter with the enumeration value navOpenInNewWindow. Obviously, such a call triggers the NewWindow2 event. The message handler of the combo box selection looks like this:

void CMainFrame::OnNewAddress()
{
    CString str;
    m_wndAddress.GetLBText(m_wndAddress.GetCurSel(), str);

    CMDIChildWnd* pChildFrame = MDIGetActive();
    if (!pChildFrame) return;

    CMfcieView* pView =(CMfcieView*)pChildFrame->GetActiveView();
    if (!pView) return;

    pView->Navigate2(str, m_bOpenNew? navOpenInNewWindow: 0);
}

Load Spy

Besides NewWindow2, there are some events that provide useful page load information. For example, the StatusTextChange event gives action indications displayed in the status bar, but it is updated too fast to be seen. The other events, such as BeforeNavigate2 and DownloadComplete, let you intercept the information about HTML, scripts, and pictures downloaded in sequence from beginning to end. So in Figure 1, you can turn on the widget by pressing down the Load Spy button, and thus MDIIE keeps logging any load information into a file. You can stop logging by toggling up the button. The logged file, such as LoadSpy.txt, is available under the TEMP specified directory, and may help you in tracing analysis.

The log record is based on the seven relevant events. The Event Log Options dialog, as shown in Figure 5, lets you choose events in which you are interested and change the filename. As you see, I abbreviate the events on the right column that can be simply recognized in the log file, such as ">BN" for BeforeNavigate2. The detailed event description is available in SDK Web Service documentations.

A log file may look like Listing 2, where I first load a local XML file and then open the CNN page in another browser view. Here the ellipsis is used for saving space. Note that in each line, the hexadecimal number in parentheses indicates the current address of a CMfcieView object (i.e., a browser instance). More interestingly, when a new WebBrowser instance is spawned in the NewWindow2 handler, you can monitor the actions for both instances. Listing 3 illustrates this scenario, where the information of the parent CNN (007D56B0) and its child "Netcenter: Public Sign In" (007D6AA0) is logged when you click on the Mail icon in the CNN page (see Figure 1); a familiar example is mentioned again in Figure 4.

The Load Spy implementation is quite straightforward. I add three data members to the mainframe: m_dlgEvOpt of the Options dialog, m_bLoadSpy of BOOL, and m_fLog of CStdioFile. In CMfcieView, I use them to log messages from the browser events. This mechanism makes it possible to observe the information of multiple browser instances. Listing 4 shows the handler of StatusTextChange.

Intermediate Window Problem

Sometimes when you click a link from a web site, it first opens a new browser window, which quickly opens another new window while making itself blank. This may occur to a restricted site where the server first opens a member-allowed window, but if the validation fails, it generates another login window while leaving itself empty. In this case, the NewWindow2 event is triggered twice and the Load Spy detects three CMfcieView instances in one processing. The problem is that in MDIIE, the empty window will stay unclosed if no further action is taken.

My solution, as shown in Listing 5, is the function CleanEmptyWnd() in the mainframe. I use the WebBrowser's method GetLocationName() to check a child browser's URL, and if its URL is empty, destroy the child window. To invoke this function, I add the flag m_bClean in the mainframe. I set it to TRUE in OnNewWindow2() (Listing 1) for the indication of any possible cleanup, and in OnStatusTextChange() (Listing 4), call CleanEmptyWnd() if necessary. At last, the m_bClean is cleared in CleanEmptyWnd().

This cheaper solution works in MDIIE just for the demonstration of browser events. There must be more sophisticated approaches available for this problem in practice, for which the discussion should involve more Internet programming techniques far beyond the current topic.

Summary

This article considered the MDI implementation of the web browser with the Microsoft WebBrowser component. By extending the sample MFCIE to my demo MDIIE, I presented some event processing that may help you to embed a web browser into your app. In terms of MDI, more enhancements can make MDIIE smarter. For example, saving the workspace for a set of child browser windows with their URLs and later loading the layout once again would be very helpful. Another preferable architecture would be an Excel-styled MDI that contains multiple tabbed web pages for fast switching. My own examples of such techniques can be found in References.

Also, you need to pay attention to MDI implementation details by reading the code online when using it. Notice that I added OnFileNew() to the App class, but this kind of new window is opened without firing the NewWindow2 event. OnFileOpen() resides in the view class to open a URL in the current browser view. Other differences between SDI and MDI include setting document titles and activating views.

Once again, MDIIE is only a lightweight demo for programming reference. It cannot be compared with commercial products. Actually, it has some unconsidered technical problems as well, such as the intermediate window, and it may inherit bugs from the original MFCIE. However, it is sufficient to give you the look and feel of an MDI web browser and make you familiar with the related events. Regarding the WebBrowser itself, it's better to keep your eyes on the articles posted about BUG, FIX, and HOWTO in MSDN.

References

Internet Explorer architecture and Web Browser: see "Reusing Browser Technology" in MSDN documentation Platform SDK/Web Services/Workshop, or online at http://www.microsoft.com/msnd/sdk/inetsdk/help.

The sample MFCIE: See VC++ sample under VC98\mfc\internet\mfcie in the MSDN CD, or download from msdn.microsoft.com.

Article Q184876: "HOWTO: Use the WebBrowser Control NewWindow2 Event."

Aaron Skonnard, "Internet Client SDK, Part I: Web Browsing Objects." MIND, October 1997.

Zuoliu Ding, "Preventing Invisible MDI Child Windows after Sizing" (Tech Tip). Windows Developer Magazine, February 2002.

Zuoliu Ding, "A Multi-Page, Single-Document UI for MFC." Windows Developer's Journal, November 2000.

Zuoliu Ding, "Managing Window Workspaces in MDI." Windows Developer's Journal, September 1999.


Zuoliu Ding has worked in programming, research, and has been teaching for over 10 years. He is now an adjunct faculty in the Computer Science Department at Fullerton College, California. He can be visited at http://staffwww.fullcoll.edu/zding/.


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.