William P. Meyers
March 23, 2005
Frames are ubiquitous on Web pages. Information about creating them, both from a design-mode standpoint or from a programmable standpoint, is readily available. Manipulating frames (and their content) from the client side in Visual Basic .NET, however, has not been well-documented. If you want to be able to program client-side DHTML, knowing how to play with frames is essential.
This article will introduce you to manipulating frames from the client-side Microsoft Internet Explorer (IE) model. Visual Basic .NET is object-oriented and has powerful libraries that aid the process. The Visual Studio .NET development environment also simplifies the task.
Having opened Visual Studio and chosen Visual Basic .NET as the programming language, click the New Project button on the Start page (or File, Project, New Project from the menus). In the New Project dialog box, choose the Windows Application template, name the Project FramesDemo, and change the location of the project if you don't like the default. Now you should see Form1 in the design view. Enlarge Form1 to a size suitable for your screen.
For this demo we will use a WebBrowser object, which is essentially a window inside a form that has most of the functionality of IE, without the buttons. We don't need the buttons because our intent is to control IE with program code.
Unfortunately the WebBrowser object is not in the Toolbox by default. Right-click the toolbox and choose Customize Toolbox. Click the Com Components tab and scroll down to Microsoft Web Browser; check the box, then click OK. An icon labeled Explorer appears in the Toolbox. Drag and Drop it on Form1. Size it to cover about half of the form.
In the Properties box you'll see you've created AxWebBrowser1. I will use this default name, but you could change it in the Properties box under (Name).
You will also need to add the Microsoft HTML Object library. Do this selecting Project, Add Reference, COM tab, scrolling to Microsoft HTML Object library, selecting it and OK. If you open the Solution Explorer window and then select References, you will see it listed as MSHTML.
AxWebBrowser1 is an object derived from the AxSHDocVw object. For our purposes AxWebBrowser1 is the IE object. AxWebBrowser1 has a large number of properties, classes, and methods, most of which are not needed for this demo.
The Document object of AxWebBrowser1 corresponds to what you see in the window of the Web browser. This (or any) Document object will have a parentWindow, which is of the type mshtml.IHTMLWindow2.
If IE displays frames, the parentWindow will have a frames collection, which is encompassed in the mshtml.FramesCollection object. This FramesCollection object contains items [like myFramesCollection.item(1)], which are the actual frame objects. Since a frame object is essentially a bundle of HTML, it is identical in structure to a window, so it can be freely converted to an IHTMLWindow2. Frames have names, which are derived by getting the name property of an IHTMLWindow2 object.
Once you have put the frame item into a window object, you probably will want the HTML inside it. You have to drill down the object hierarchy again! Each window has a Document object (that is why we could get a parentWindow earlier), which in turn has a body, which in turn has something called outerHTML, which is just text. So to assign the HTML of oWin1 to a String you need the following statement:
MyString = oWin1.document.body.outerHTML
Fortunately Visual Studio helps you quite a bit. If you start typing in the previous statement, when you type the first period it will give you a list of the objects, properties and methods that oWin1 has. The same help occurs each time you type a period.
Confused? The zig-zag down from the WebBrowser to the document then up to a window then back down to the FrameCollection, which turns out to hold frames which are their own windows with documents and bodies, takes some getting useful. Figure 1 may help.
Also note that frames can have sub-frames. Then the frame Window object has its own FramesCollection. This example will not illustrate sub-frames, but the principles are the same.
Double-click on Form1 (not on the browser window) to open up its coding area. At the top, before Public Class Form1, type Imports mshtml (you will get an error if you did not reference MSHTML as shown earlier).
Immediately under Inherits System.Windows.Forms.Form, you can enter the global variables you will need to manipulate IE objects down to the frames and subframes. Type in the following:
Dim UrlBase As String 'For
the URL of the Web site you will explore
Dim ViewString As String 'For showing HTML from captured frames
Dim FrameName1 As String 'Frames have names you can store here
Dim oDoc1 As mshtml.HTMLDocument 'for Document objects
Dim oWin1, oWin2 As mshtml.IHTMLWindow2 'for Window objects
Dim oFrameC1 As mshtml.FramesCollection 'for FrameCollection objects
Note that you don't need to create frame objects, as the frames themselves are Window objects.
I made an example Web page with frames (referred to as a frameset) for testing the sample code. If you are not familiar with how frames are constructed, you should look at it in your regular IE browser now. The URL is http://www.openicon.com/FrameSetExample.html. You will see 3 frames, each containing only some identifying wording. The relevant section of HTML is:
<FRAMESET FRAMEBORDER=1 COLS="18%,82%">
<FRAME SRC="FrameExample1.html" NAME="Frame27899">
<FRAMESET FRAMEBORDER=1 ROWS="24%,76%">
<FRAME SRC="FrameExample2.html" NAME="Frame17786">
<FRAME SRC="FrameExample3.html" NAME="Frame8685">
This frameset page just tells the browser where the frame borders are and where to go to get the content of each of the frames. The frames do not have to be at the same Web site as the frameset, though in this example they do.
First, there is a trick that could save you a lot of troublesome programming. From the source HTML you can see the URLs for the individual frames. Type www.openicon.com/FrameExample1.html into the address bar of IE, for example, to see the frame unframed.
Now it is time to code for navigating your AxWebBrowser1 to the sample page. In Form1 create a subroutine after the Windows Form Designer generated code.
Private Sub NavBrowser()
____Dim oNull As Object
____oNull = ""
____UrlBase = "http://www.openicon.com/FramesetExample.html"
____AxWebBrowser1.Navigate(UrlBase, oNull, oNull, oNull, oNull)
The first parameter of the Navigate method takes a string to specify the URL. Other parameters of Navigate are useful for setting flags, selecting a frame, and posting data back to the server.
To see if this routine works, create a button on Form1 (in the design view), double click, and insert NavBrowser() as the code to be executed. If you run your code, have a connection to the Internet open, and click the button, you should see the page and its frames in the browser section of your form.
If you control each step of your process with command buttons, you can wait to see the Web page is loaded before starting to process it or downloading something else. But the idea here is to do things programmatically. Fortunately AxWebBrowser1 has an event called DownloadComplete. By creating a handler for DownloadComplete you can have your program move forward at the right moment.
Frames complicate this a bit. The frameset page and each of its frames, when downloaded, generate a DownloadComplete event. To keep this example simple I'll add a global variable DownCount to count the downloads. When 4 have taken place the frame manipulating routine will be called.
To set up your handler for DownloadComplete, go to your Form1 code tab. In the left dropdown list select AxWebBrowser1. Now if you look at the choices in the right side dropdown list you'll see the events you can capture. Choose DownloadComplete. Visual Studio adds a subroutine, to which you need to add some code to get the following:
Private Sub AxWebBrowser1_DocumentComplete(ByVal
sender As Object, ByVal e As AxSHDocVw.DWebBrowserEvents2_DocumentCompleteEvent)
____DownCount = DownCount + 1
____If DownCount = 4 Then
________DownCount = 0 resets DownCount
Also add the declaration of DownCount at the top of the Form1 coding space:
Dim DownCount As Integer
Open the Form1_Load subroutine by double clicking on Form1 in the design view and entering:
DownCount = 0
This will execute at when Form1 is loaded. Other initializations can be placed here as well.
In a program that generates more than a few DownloadComplete events, it may make sense to use another method, for instance allowing a specific amount of time for a download and then testing to see if the page you require has actually downloaded.
There are two methods for getting the HTML from a particular frame. One is to use Navigate to download the frame you want to examine into the browser by itself. Then the HTML can be got with a two statements:
oDoc1 = AxWebBrowser1.Document
MyString = oDoc1.body.outerHTML
However, you had to download the same page twice to do that, and if you want more frames, you must download each of them. They were all there with the first download.
Instead, let us count how many frames there are, and then allow the computer to display the HTML of each in a message box. You could also display them in other controls, forms or windows.
Create a new subroutine:
Private Sub Frames()
Dim CountFrames, i As Integer
oDoc1 = AxWebBrowser1.Document
Get the document
oWin1 = oDoc1.parentWindow Get the Window for the document
oFrameC1 = oWin1.frames Get the FrameCollection
CountFrames = oFrameC1.length length is the number of frames
Frame item numbering starts at 0, so:
For i=1 To CountFrames - 1
____MsgBox("Frame # is " & i)
____oWin2 = oFrameC1.item(i)
____FrameName1 = oWin2.name
____MsgBox("FrameName is " & FrameName1)
____ViewString = oWin2.body.outerHTML
If there are no bugs, when you press your button the page will download. The names of the individual frames and their contents will be displayed in message boxes.