Chapter 14

Controls That Interact with the Internet


CONTENTS

This chapter discusses how to add Internet features to your control. The Internet features I address fall into two broad categories:

Whether your control is appropriate to use in an EXE or a Web page context, or both, will depend on its functionality, as well as safety issues discussed in Chapter 13. This chapter will discuss both types of controls. In addition, we'll tie in the topics of distributing your control and using your ActiveX control in a Web context that we covered in Chapters 12 and 13.

NOTE
A few of the examples in this chapter require Microsoft Internet Explorer. You can download Internet Explorer free from the Microsoft Web site at http://www.microsoft.com/ie/download/.

Asynchronous Download of Property Values

One of Visual Basic 5.0's new features is the ability of ActiveX controls to asynchronously download property values. This feature enables your control to read a property from a remote source-generally, the Internet. The fact that the download is asynchronous means that your application does not have to wait for the download to complete before proceeding with other processing. Because files can take a while to be transmitted over the Internet, the asynchronous download feature is very useful indeed.

The HappyHour control, comprised of a constituent PictureBox and Label controls, was introduced earlier in this book. Because it is designed to display a different picture and text message depending on the time of day, it is an ideal demonstration of the power of asynchronous download over the Internet.

This new version of the HappyHour control has the following programmable interface:

Using the AsyncRead Method

In order to access the graphics denoted by the HappyHourYesURL and HappyHourNoURL properties, your control will need to download them. You use the UserControl object's AsyncRead method to do this. Because AsyncRead is an asynchronous process, as soon as you call this method, processing continues while the property downloads in the background. This processing could include, for example, additional input by the user or even the download of additional data from another source, since you can have a hypothetically unlimited number of downloads going at the same time. Even if you don't plan on enabling your control to perform other actions while it's asynchronously downloading a property value, it's a good idea to asynchronously download properties your control gets from the Internet, since you often have no control over how long something takes to download over the Internet.

The syntax of the AsyncRead method is:

UserControl.AsyncRead strURL, lngAsyncType, [Property]

The argument strURL is the target URL from which to download the property. The lngAsyncType argument is an enumeration that specifies what kind of property is being downloaded. The legal values for this argument are:

The Property argument is an optional identifier. It does not assign the download to a property, as you might think it would. Instead, it gives a name to the download so you can cancel it later if you need to. (You would use the CancelAsyncRead method to cancel the download.) The property name you assign here is also used to assign the downloaded data to a property; this is done in the AsyncReadComplete event, which is fired after the download is complete.

To see how this works, begin by opening the version of the HappyHour control in the Chapter 14\HappyHour4\Before folder in the CD-ROM that accompanies this book. The project group file is HappyHour.vbg.

The HappyHour Control's URL Properties

In the previous iteration of this control, the HappyHour control raised a HappyHour-Changed event when it was time for happy hour. This new iteration of the control continues to raise this event but also adds two new properties, HappyHourYesURL and HappyHourNoURL. These two URLs point to graphic files on the Internet that are loaded when the HappyHourChanged event is triggered. When it's happy hour, the graphic located at HappyHourYesURL is loaded. When it's not happy hour, the graphic located at HappyHourNoURL is loaded.

To enable these new properties, do the following:

  1. Add the two new properties to the happy hour control. The complete list of private property declarations should look like this:
' Properties (in declarations section)
Private mdatHappyHourBegin As Date
Private mdatHappyHourEnd As Date
Private mstrHappyHourYesURL As String
Private mstrHappyHourNoURL As String
  1. Declare the internal variable mvHappyHour as a Variant. This private variable is used by the control as a flag to determine whether it is currently happy hour or not. (It is declared as a Variant instead of a Boolean so the control can perform a test to determine whether the variable was initialized or not; this is an enhancement to this version of the HappyHour control.)
' Internal variables
Private mvHappyHour As Variant
  1. The existing event declaration for the HappyHourChanged event should remain as-is, as shown below. The declarations for this control are now complete.
' Events
Public Event HappyHourChanged(HappyStatus As Variant)
  1. Next, to save the control's properties' design-time values, add code for them in the ReadProperties and WriteProperties events of the UserControl. The code in events (including the code that was there previously) should now look like this:
Private Sub UserControl_ReadProperties(PropBag As PropertyBag)
On Error Resume Next
    mstrHappyHourYesURL = PropBag.ReadProperty("HappyHourYesURL", "")
    mstrHappyHourNoURL = PropBag.ReadProperty("HappyHourNoURL", "")
    Caption = PropBag.ReadProperty("Caption", Extender.Name)
    mdatHappyHourBegin = PropBag.ReadProperty("HappyHourBegin")
    mdatHappyHourEnd = PropBag.ReadProperty("HappyHourEnd")
End Sub

Private Sub UserControl_WriteProperties(PropBag As PropertyBag)
    PropBag.WriteProperty "HappyHourYesURL", mstrHappyHourYesURL, ""
    PropBag.WriteProperty "HappyHourNoURL", mstrHappyHourNoURL, ""
    PropBag.WriteProperty "Caption", Caption, Extender.Name
    PropBag.WriteProperty "HappyHourBegin", mdatHappyHourBegin
    PropBag.WriteProperty "HappyHourEnd", mdatHappyHourEnd
End Sub
  1. Enter Property Get and Property Let procedures for these new properties:
Public Property Get HappyHourYesURL() As String
    HappyHourYesURL = mstrHappyHourYesURL
End Property

Public Property Let HappyHourYesURL(ByVal strNewValue As String)
    mstrHappyHourYesURL = strNewValue
    If mvHappyHour Then
        HappyHourLoadGraphic True
    End If
    PropertyChanged "HappyHourYesURL"
End Property

Public Property Get HappyHourNoURL() As String
    HappyHourNoURL = mstrHappyHourNoURL
End Property

Public Property Let HappyHourNoURL(ByVal strNewValue As String)
    mstrHappyHourNoURL = strNewValue
    If Not(mvHappyHour) Then
        HappyHourLoadGraphic False
    End If
    PropertyChanged "HappyHourNoURL"
End Property

The LoadHappyHourGraphic subroutine, called from the Property Let procedures of the new properties, initiates the asynchronous download. The code for this subroutine looks like this:

Private Sub LoadHappyHourGraphic(HappyStatus)
' Loads the appropriate happy hour graphic

If HappyStatus = True Then
    AsyncRead mstrHappyHourYesURL, vbAsyncTypePicture, _
    "asyncHappyHourYes"
Else
    AsyncRead mstrHappyHourNoURL, vbAsyncTypePicture, _
              "asyncHappyHourNo"
End If

End Sub

Putting the AsyncRead in its own subroutine helps you to avoid duplicating code in your project, since (as you'll see) the AsyncRead method must be called in several different places. Although the AsyncRead method reads the property from the Net, the property can't be assigned until the download is complete. This assignment takes place in the AsyncReadComplete event.

In order to assign the downloaded data to a property, the AsyncReadComplete event of the UserControl is passed an AsyncProperty object. The AsyncProperty object represents the property that was downloaded. This object has three properties of its own:

To handle an incoming asynchronously downloaded property, enter the following code:

Private Sub UserControl_AsyncReadComplete(AsyncProp As AsyncProperty)
    If AsyncProp.AsyncType = vbAsyncTypePicture Then
        Picture1.Picture = AsyncProp.Value
    End If
End Sub

This code is simple because there are only two types of asynchronous downloads handled by the HappyHour control-one that downloads the Happy Hour graphic, the other that downloads the "Get back to work" graphic-and they are both assigned to the same thing (the Picture property of Picture1).

However, if your control had to download many different kinds of files over the Net, you'd have to set up a Select Case based on AsyncProp in the AsyncReadComplete event. The code would look like this:

Private Sub UserControl_AsyncReadComplete(AsyncProp As AsyncProperty)
    Select Case AsyncProp.PropertyName
        Case "asyncHappyHourYes"
        ' assign AsyncProp.Value to the property

        Case "asyncHappyHourNo"
        ' assign AsyncProp.Value to the property

        Case "asyncSomethingElse"
        ' and so forth

    End Select
End Sub

Finally, in order to cause the control to download the property at happy hour, change the Timer event of the constituent Timer control to call the same LoadHappyHourGraphic subroutine you called in the control's Property Let procedures. The code for the Timer event should look like this:

Private Sub Timer1_Timer()
' Raise the appropriate event based on
' whether it's happy hour or not

' happy hour hasn't been set yet
    If mdatHappyHourBegin = 0 Or mdatHappyHourEnd = 0 Then
        Exit Sub
    End If

Select Case mvHappyHour
    Case Empty  ' i don't know whether it's happy hour or not
        If Time > mdatHappyHourBegin And _
           Time < mdatHappyHourEnd Then
           mvHappyHour = True
        Else
           mvHappyHour = False
        End If
        LoadHappyHourGraphic mvHappyHour
        RaiseEvent HappyHourChanged(mvHappyHour)
        
    Case True   ' it was happy hour a second ago
        If Time > mdatHappyHourBegin And _
           Time < mdatHappyHourEnd Then
        ' it's still happy hour; do nothing
        Else
            mvHappyHour = False
            LoadHappyHourGraphic (False)
            RaiseEvent HappyHourChanged(False)
        End If
    
    Case False  ' it was not happy hour now a second ago
        If Time > mdatHappyHourBegin And _
           Time < mdatHappyHourEnd Then
        ' it's now happy hour
            mvHappyHour = True
            LoadHappyHourGraphic (True)
            RaiseEvent HappyHourChanged(True)
        Else
        ' do nothing
        End If

End Select

End Sub

There's nothing particular tricky about this event procedure; all of the work is done in the subroutines you entered previously.

What the Timer Event Does

The code in the Timer event first checks the variables that store happy hour's begin and end times. If either of these is set to zero (that is, it has not yet been initialized), then the event bails out, because it would make no sense to proceed if the control didn't know when happy hour is. Note that if mdatHappyHourBegin and mdatHappyHourEnd were Variant instead of Date values, you'd instead use the IsEmpty function to determine if the variables had not yet been initialized.

Once the procedure has determined when happy hour is, it compares that value to one of three previous states for happy hour: True, False, or uninitialized (Empty). This comparison takes place in the Select Case in the Timer event.

Based on this comparison, the procedure either raises the HappyHourChanged event and downloads the appropriate graphic, or (if the state of happy hour has not changed), does nothing.

Testing the Code

Now that you've set up the new asynchronous download properties, you can test the control. To do this:

  1. Close the code window and close the HappyHour control's designer. Switch to the EXE test form frmHHTestForm.
  2. Click on the HappyHour control on the EXE project test form to select it.
  3. In its HappyHourYesURL property, type the URL of a graphic, either on your local machine or on the Internet. Type the URL of a different graphic in the HappyHourNoURL property. (If you don't have Net access or you just don't want to come up with a URL of your own, you can use the files happy.bmp and work.bmp on the CD-ROM that accompanies this book.)
  4. Enter time values for HappyHourBegin and HappyHourEnd. Make HappyHourBegin a few minutes from now (according to your computer's clock). The Properties window should look like Figure 14.1.
    Figure 14.1 : Properties window for HappyHour control.

  5. Depending on whether it's happy hour or not, one of the graphics will appear. (This may take a moment, depending on the size of the file, whether you selected a local file or an Internet file, and the speed of your Internet connection.)
  6. Wait a few minutes for the state of the control to change from non-happy hour to happy hour. When it becomes time for happy hour, you should be able to see the control download the happy hour graphic you specified in its HappyHourYesURL property, as illustrated in Figure 14.2.
    Figure 14.2 : Downloaded HappyHourYesURL property.

Internet Controls as Constituent Controls

You can include an Internet control as a constituent control in order to give your project Internet capabilities. Internet controls handle such tasks as sending e-mail and transferring files (using file transfer protocol, or FTP). There are also Internet controls that give you direct access to Net connections, so-called socket controls that enable you to build your own Internet-aware application from scratch without having to worry about how the network transport works.

In this demonstration, you'll build a control that will act as a custom technical support request interface. This control could be used in situations where you need to deploy one or more applications that supply a tech support e-mail feature. Providing an ActiveX control to enable users to send e-mail to tech support would not only be useful in a number of different applications, but it would also provide a consistent interface across those different apps.

NOTE
The Internet control I used for this demonstration is a shareware mail control produced by Mabry Software. There are a few suites of Internet controls on the market; I chose Mabry's because I like their liberal shareware policy-and they make good stuff. You can find Mabry on the web at http://www.mabry.com.
In 1996 Microsoft briefly made a set of Internet controls available called Internet Control Pack. These controls have since been "transferred" to NetManage, and you can find them on the Web at http://www.netmanage.com.

Obtaining the Mabry Mail Control

You can obtain a shareware version of the Internet mail control used in the following demonstration. This control is available as part of the Mabry Internet Pack, downloadable from Mabry Software's Web site at http://www.mabry.com. Information on the Internet Pack is at http://www.mabry.com/ipack.htm; the downloadable shareware version of the controls are at ftp://ftp.mabry.com/ipack.exe.

Once you've obtained ipack.exe and installed it on your system, the complete set of Mabry Internet controls is available on your system. You'll use one of these controls in the following demonstration.

Building the TechSupport Control

To build the TechSupport control, do the following:

  1. Create a new control project. Open the UserControl's designer. Give the UserControl the name TechSupport.
  2. Add a list box, command button, and text box control to the control designer. Give the command button the name cmdSend. Give the text box the name txtMessage. Give the list box the name lstDepartment.
  3. Set the Caption property of cmdSend to Send.
  4. Set txtMessage's MultiLine property to True.
  5. Set the Text property of txtMessage to Type your message here, then click on the Send button. The control designer should look like Figure 14.3.
    Figure 14.3 : TechSupport control designer.

Next you'll add code to initialize the control. The idea here is to give the user the ability to send a tech support request to any one of a number of tech support departments. To do this:

  1. Double-click on the control designer to open its code window.
  2. In the Initialize event of the UserControl, type the following code:
Private Sub UserControl_Initialize()
    lstDepartment.AddItem "Hardware Support"
    lstDepartment.AddItem "Software Support"
    lstDepartment.AddItem "Athletic Support"
End Sub

Next, you'll put code in the command button's Click event to validate that the tech support message has been created correctly. To do this:

  1. In cmdSend's Click event, type the following code:
Private Sub cmdSend_Click()
    If lstDepartment.ListIndex = -1 Then
        MsgBox "Please choose a tech support department " & _
               "from the list.", vbExclamation, "Tech Support"
        lstDepartment.SetFocus
        Exit Sub
    End If
    
    If txtMessage.Text = "" Then
        MsgBox "Please type a message.", vbExclamation, "Tech Support"
        txtMessage.SetFocus
        Exit Sub
    End If
    
    ' OK to send the message
    SendMessage
End Sub
  1. The SendMessage subroutine, called at the end of the Click procedure, is the procedure that actually sends the mail; you'll write that procedure in a later step. First, to make the SendMessage work, type the following enumeration and module-level declaration in the Declarations section of the code window:
Private State As EmailState

Enum EmailState
    StateSending = 1
    StateConnecting = 2
    StateDisconnecting = 3
End Enum

Now you're ready to add the mail control and write the code that will drive it. To do this:

  1. Select the menu command Project, Components. In the list of available components, check Mabry Internet Mail control. The Components dialog box should look like Figure 14.4.
    Figure 14.4 : Components dialog box.

  2. Click on OK. The Mabry Mail control appears in the Toolbox, as illustrated in Figure 14.5.
    Figure 14.5 : Mail control in Toolbox.

  3. Using the Toolbox, add an instance of the mail control to the control designer. If you're using the shareware version of the mail control, an information window (popularly known in the downscale world of shareware as a nag screen) pops up. Wait a few seconds, then click on the nag screen to make it go away.

NOTE
The nag screen appears because this control, like many ActiveX controls, is distributed as shareware. Shareware means that you can try the software before you purchase it; if you use it, you're expected to pay the author for it. Additionally, if you register the control by purchasing it, the nag screen goes away. Mabry Software has a very liberal shareware policy; all of their controls are available for download as shareware.

  1. An instance of the Mabry Mail control appears on your control designer. Don't worry about where this control is positioned on the designer; it will be invisible at runtime.
  2. The code that will send the mail message exists in two places: the SendMessage subroutine and the Done event of the mail control. The following code implements the SendMessage subroutine.

NOTE
The best way to test an application like this is to set it up to send e-mail messages to yourself (as opposed to, for example, me). Be sure to replace those bogus e-mail addresses with your own, so that when you're testing this control you'll actually get some mail.
Private Sub SendMessage()
' Sends an email message

Select Case lstDepartment.Text
' For testing purposes, you'll want
' to replace one or all of these
' email addresses with real Internet addresses.

    Case "Hardware Support" ' replace with your email address
    mMail1.To = "hardware@support.com"
    
    Case "Software Support" ' replace with your email address
    mMail1.To = "software@support.com"
    
    Case "Athletic Support" ' replace with your email address
    mMail1.To = "athletic@support.com"  
    
End Select

    mMail1.Subject = "Tech Support Request"
    mMail1.From = "Hapless User <hapless@user.com>"

' Replace the value in the next line with your
' internet service provider's email server; usually
' "mail.yourisp.com".
    mMail1.Host = "mail.your_internet_service_provider.com"

' Replace the value in the next line with the
' name of your email account. This value will not
' appear in the email message; it's used to log in
' to your outgoing email server (if the server 
' requires such a login).
    mMail1.EMailAddress = "<your_account@yourisp.com>"
        
    mMail1.Body(0) = txtMessage.Text

    Screen.MousePointer = 11
    State = StateConnecting
    mMail1.Action = MailActionConnect
    
    If (mMail1.Blocking = True) Then
       mMail1_Done
    End If

End Sub

The important part of this code is toward the end of the procedure. This code calls the Done event of the mail control three times, once for each stage of the e-mail session (sending, connecting, and disconnecting).

To finish the project, put the following code in the mail control's Done event:

Private Sub mMail1_Done()
    Screen.MousePointer = 0
    
    Select Case State
        Case StateConnecting
            State = StateSending
            mMail1.Flags = MailDstIsHost
            mMail1.Action = MailActionWriteMessage
            If (mMail1.Blocking = True) Then
                mMail1_Done
            End If
        Case StateSending
            State = StateDisconnecting
            mMail1.Action = MailActionDisconnect
            If (mMail1.Blocking = True) Then
                mMail1_Done
            End If
        Case StateDisconnecting
            'do nothing; let it finish
    End Select

End Sub

You can see that this event procedure can do three things depending on the state of the session with the mail server. If the event is raised after the control has finished connecting with the server (the StateConnecting case), the procedure changes the State flag to Sending, then sends the message. If the event is raised after the control has sent the message (the StateSending case), the event procedure changes the State flag to Disconnecting and disconnects from the server. If the event is raised after the control has disconnected (the StateDisconnecting case), then the procedure does nothing, because its work is done.

This control project now has all it needs to blanket the world (or you, at least) with useless e-mail. To test the control, do the following:

  1. Close the code window and the control designer.
  2. Open the test EXE project form.
  3. Place an instance of the control on the form.
  4. Run the project by choosing the menu command Run, Start, or by pressing the function key F5.
  5. The project runs. Select a tech support department from the list box, then type a message in the text box. Click on Send to send the message.
  6. If you modified the code so it sends e-mail to yourself, wait a few minutes, then check your mail (using whatever application you normally use to check your mail). The mail message should appear in your incoming e-mail.

Although this is an extremely simple example of sending Internet e-mail, the fact that you have programmable control over the process opens up a number of interesting possibilities. You could write a control that polls a database once per hour, notifying a system administrator when a query retrieved a particular set of results. Or you could modify the control to generate a mass mailing to everyone in your company once per day, thereby introducing you to the exciting and dynamic world of independent consulting.

About the Winsock API

Any Windows application that does anything with the Internet does so through a networking API known as Winsock. Winsock implementations have existed for every version of Windows since 16-bit Windows 3.1.

In this chapter, I wanted to include an example of how to write a Winsock control using native Winsock API calls, but I quickly realized that such a project could very well take up a book of its own-and, in fact, somebody else has already written that book. The book is Michael Marchuk's Building Internet Applications With Visual Basic (Que, 1995), and it's definitely one of a kind. This book will really give you an insight into how to get to the Internet from Visual Basic. Its only problem from the perspective of a Visual Basic control creator is that all the examples and API declarations are geared toward the 16-bit implementation of Winsock.

What's Going On under the Hood

In order to appreciate how much functionality is encapsulated by the mail control, you might find it helpful to see the actual text of the conversation it has with the server when it sends your e-mail message. You can get a look at the conversation the mail control has with the mail server by activating its debug mode. To do this:

  1. Open the control designer and click on the mail control. In the Properties window, set the control's Debug property to 1.
  2. Double-click on the mail control to bring up its code window. Using the Procedure combo box, switch to the mail control's Debug event.
  3. In the Debug event, write the following code:
Private Sub mMail1_Debug(ByVal Message As String)
    Debug.Print Message
End Sub
  1. Close the code window and the control designer. Go back to the EXE test form, launch it, and send yourself another e-mail message.
  2. In the Immediate window, the following conversation appears:
OnSend
220-mail1.sirius.com Sendmail 8.6.12/960710 ready at Mon, 23 Dec 1996 
12:10:50 -0800
220 ESMTP spoken here

HELO bedrock

250 mail1.sirius.com Hello ppp011-sf1.sirius.com [205.134.227.11], 
pleased to meet you

MAIL FROM:"Skippy" <skippy@sirius.com>

250 "Skippy" <skippy@well.com>... Sender ok

RCPT TO:skippy@sirius.com

250 skippy@sirius.com... Recipient ok

DATA
354 Enter mail, end with "." on a line by itself

250 MAA19061 Message accepted for delivery

quit

221 mail1.sirius.com closing connection

NOTE
This is roughly what I get when I send mail through my Internet service provider (ISP), anyway. The exact words you'll see will depend on how your ISP's mail server is configured.

From looking at the debug information, you can see that the mail control really works on two levels. It handles the Winsock connectivity behind the scenes, acting as a wrapper around the Winsock API calls and encapsulating the Internet mail protocol, known as Simple Mail Transfer Protocol (SMTP). This protocol governs the way applications send mail through Internet mail servers. Again, it isn't strictly necessary for you to know how this works to add Internet mail functionality to your application, but it might prove helpful if you ever wanted to write your own Internet mail control.

Adding Web-Browsing Features to Your Control

Although there are a few ActiveX controls on the market that enable Web browsing and downloading of Internet data, none are likely to have an interface as rich (or well-supported) as that of Microsoft Internet Explorer. So it's fortuitous that Internet Explorer is a free download. Someday (hopefully soon) ActiveX will be supported on all operating system platforms, so you'll be able to activate Web browsers everywhere.

Internet Explorer's object interface can be used by Visual Basic applications in two ways: as an ActiveX control (the WebBrowser control) and as an Automation object. We'll take a look at both of these techniques in the remainder of this chapter. Then, in Chapter 16, "Object-Oriented Programming," you'll see how to put these technologies to work in the form of a custom Web-browsing Hotlist control.

Using Internet Explorer as a Constituent Control

One of the neat things about Microsoft Internet Explorer is the fact that it is built around an ActiveX control. This means you can use Internet Explorer as a constituent control in your applications; the only requirement is that your users have Internet Explorer installed on their computers-not a particularly steep requirement, since it's a free download.

NOTE
The full documentation of the object model of the WebBrowser control is available on the Microsoft Web site at http://www.microsoft.com/intdev/sdk/docs/iexplore/. It's definitely worth downloading if you plan on building a control based on the WebBrowser object. There are separate versions of the documentation geared toward Visual Basic and C/C++ developers.

The ActiveX implementation of the WebBrowser control is in a file called shdocvw.dll; it is registered in the list of ActiveX components as Microsoft Internet Controls.

NOTE
In order to keep you on your toes, Microsoft did not give the WebBrowser ActiveX control an .OCX extension as you'd expect; it is a .DLL file. But you knew already from your diligent reading of the previous chapters of this book that .OCX files are really just special types of .DLL files. You weren't confused by that, were you? Of course not.

In order to see an example of the WebBrowser control in a control project, you'll build a control that will display Web pages automatically, one after the other, in a slide show format. To do this:

  1. Start a new control project. Give the UserControl the name NetSlideShow.
  2. Add a Timer control to the control designer.
  3. From the Project menu, select Components. Scroll through the list until you find Microsoft Internet Controls, then check it. The dialog box will look like Figure 14.6.
    Figure 14.6 : Components dialog box.

  4. Click on OK. The WebBrowser control is added to the Toolbox, as illustrated in Figure 14.7.
    Figure 14.7 : WebBrowser control in Toolbox.

  5. Add an instance of the WebBrowser control to the control designer.

NOTE
In the beta version of Visual Basic 5.0 used to develop the examples in this book, the WebBrowser control did not repaint itself or display its borders correctly at design time. This was probably just a quirk in the beta of VB 5.0; it's likely that this will have been fixed by the time you read this. This problem does not affect the control's functionality, at any rate.

  1. Add the following code to resize the WebBrowser control to the dimensions of the UserControl. In addition to resizing the control, this code also displays a Web page; it exists in the Resize event so the control will display something as soon as it is instantiated at runtime.
Private Sub UserControl_Resize()
    WebBrowser1.Move 0, 0, ScaleWidth, ScaleHeight
    If mstrFirstURL > "" Then
        WebBrowser1.Navigate mstrFirstURL
    End If
End Sub
  1. Your control needs to expose a TimerInterval property that enables the user to specify how many seconds to display each Web page. Unlike the constituent Timer's Interval property, this property is measured in seconds, instead of milliseconds; your control will perform the conversion seamlessly. Additionally, the Property Let needs to include validation code to ensure that the user does not set the TimerInterval property to an unreasonable value. To implement this property, enter the following code:
Public Property Get TimerInterval() As Integer
    TimerInterval = Timer1.Interval / 1000
End Property

Public Property Let TimerInterval(ByVal lngNewValue As Integer)
    If lngNewValue > 0 And lngNewValue < 60 Then
        Timer1.Interval = lngNewValue * 1000
    End If
End Property
  1. Next you'll implement two properties, FirstURL and SecondURL. These properties store the URLs of the Web pages that the control will display. To do this, enter the following code:
' Declarations
Private mstrFirstURL As String
Private mstrSecondURL As String
Private mbURLFlag As Boolean  ' specifies which URL is current

Public Property Get FirstURL() As String
    FirstURL = mstrFirstURL
End Property

Public Property Let FirstURL(ByVal strNewValue As String)
    mstrFirstURL = strNewValue
    WebBrowser1.Navigate strNewValue
End Property

Public Property Get SecondURL() As String
    SecondURL = mstrSecondURL
End Property

Public Property Let SecondURL(ByVal strNewValue As String)
    mstrSecondURL = strNewValue
    WebBrowser1.Navigate strNewValue
End Property
  1. The Timer event of the constituent Timer control performs the work of downloading the appropriate URLs at runtime. To make it work, enter the following code:
Private Sub Timer1_Timer()

' Only rotate slides at run time.
If Ambient.UserMode = True Then
    If mbURLFlag Then
        If mstrFirstURL > "" Then
            WebBrowser1.Navigate mstrSecondURL
            mbURLFlag = False
        End If
    Else
        If mstrSecondURL > "" Then
            WebBrowser1.Navigate mstrFirstURL
            mbURLFlag = True
        End If
    End If
End If
End Sub
  1. Finally, add code to the ReadProperties and WriteProperties events of the UserControl so the values of its design-time properties are stored:
Private Sub UserControl_ReadProperties(PropBag As PropertyBag)
    mstrFirstURL = PropBag.ReadProperty("FirstURL", "")
    mstrSecondURL = PropBag.ReadProperty("SecondURL", "")
    Timer1.Interval = PropBag.ReadProperty("TimerInterval", 0) * 1000
End Sub

Private Sub UserControl_WriteProperties(PropBag As PropertyBag)
    PropBag.WriteProperty "FirstURL", mstrFirstURL, ""
    PropBag.WriteProperty "SecondURL", mstrSecondURL, ""
    PropBag.WriteProperty "TimerInterval", Timer1.Interval / 1000, 0
End Sub
  1. Now you can test the control. To do this, close the code window, close the control designer, and open the text EXE project form.
  2. Place an instance of the NetSlideShow control on the form. The control appears, but no URL opens, because you haven't set any of the control's properties yet.
  3. Set the control's FirstURL property to http://www.fieldguide.com. The Web page for the Field Guide to North American Males appears, as illustrated in Figure 14.8.
    Figure 14.8 : NetSlideShow control with Web page.

  4. Set the control's SecondURL property to http://www.well.com/user/jeffreyp/activex/. The home page for this book appears.
  5. Set the control's TimerInterval property to 20. (The trick here is to set the interval to a number high enough that the user can see the current picture fully loaded before the next one starts loading.)
  6. Launch the EXE project by choosing the menu command Run, Start, or by pressing the function key F5. Wait twenty seconds; the URL should change automatically.

NOTE
A more elegant way to implement the URL properties of this control might be through the use of a collection. That way, rather than limiting the number of Web pages to two, you could display a (theoretically) unlimited number of Web pages by adding each new Web page to a collection of URLs. For more information on how to implement collections, see Chapter 16, "Object-Oriented Programming."

Controls That Act as Hyperlinks

You can use the Internet Explorer object model to control a Web browser from an ActiveX control contained in the browser.

For example, consider a large Web site with large pages containing lots of links. Such sites often contain HTML hyperlinks that say Back, which is the link that takes you back to the page where you came from. But how does the page know exactly which page you came from? The answer is: it doesn't. It assumes you followed the hierarchy the Web site designer set up for you. But we all know the Web doesn't always work that way. You could have come to the site from a search engine or typed in the URL manually.

In order to have a true Back button, your control would need information about which URL the user had been previously viewing. Internet Explorer conveniently stores that information in a place called the history list. You can move through the URLs in the browser's history list by using the GoForward and GoBack methods of the WebBrowser control.

To see how this works, you'll set up a custom command button that acts as a Back button. The advantage of this control over a Back hyperlink is that it will always take the user back to the previous URL viewed. Another advantage is that it will always work, no matter which Web page you use it in. To set up this control:

  1. Create a new control project. Give the UserControl the name BackButton.
  2. Add a constituent command button to the control designer.
  3. In the command button's Click event, enter the following code:
Private Sub Command1_Click()
On Error Resume Next
    Hyperlink.GoBack
End Sub

NOTE
You include the On Error Resume Next in case some wisenheimer tries to put the control in a Visual Basic form; since only Web pages support the GoBack method, this control is only appropriate for use in a Web page.

In the Click event procedure, you use the Hyperlink object (an element of the UserControl object) to navigate through the Web browser container's history list. The Hyperlink object has three methods:

To finish this control and prepare it for testing, include the standard code for resizing and initializing the control's Caption property:

Private Sub UserControl_InitProperties()
On Error Resume Next
    Caption = Extender.Name
End Sub

Private Sub UserControl_Resize()
    Command1.Move 0, 0, ScaleWidth, ScaleHeight
End Sub

Public Property Get Caption() As String
    Caption = Command1.Caption
End Property

Public Property Let Caption(ByVal strNewValue As String)
    Command1.Caption = strNewValue
End Property

Now you're in a funny situation, because at this point you want to test the control. But you don't want to go to the trouble of testing the control on a Web page, because then you'd have to compile it and write an HTML test page for it. I thought about creating an EXE test form with a Web browser control on it, then making the Web browser control the container of the BackButton control. But that doesn't work (because the Web browser control doesn't contain controls in the traditional sense; it can contain controls, but only if those controls are embedded into the HTML).

So for now, you're stuck with compiling and writing an HTML test page for the BackButton control in order to test it. To do this:

  1. In the Project Explorer, give the control project a name, such as MyBackButton. This name will show up when you compile the control.
  2. Select the menu command File, Make MyBackButton.ocx. The Make Project dialog appears. Click on OK.
  3. Launch the ActiveX Control Pad. Insert an instance of MyBackButton.BackButton in the HTML page that's automatically generated, then save the page as BackButton.htm.

NOTE
See Chapter 13 for more information on how to use the ActiveX Control Pad.

  1. Close the ActiveX Control Pad and launch Microsoft Internet Explorer. The browser will launch and open to the default page.
  2. In Internet Explorer, select the menu command File, Open. Click on Browser to locate the file BackButton.htm you saved previously.
  3. The Web page containing the BackButton control appears. Click on it and you'll be sent back to the default page.

NOTE
If at this point you wish to go back and enhance the control you're working on, be sure to check the Binary Compatibility option (in Project Properties) before you recompile. If you don't do this, you'll need to recreate your test HTML page, because the control's GUID will change when it is recompiled.

Using the Internet Explorer Automation Object

You can launch an instance of Microsoft Internet Explorer using a technology known as Automation (formerly known as OLE Automation). Automation is an object technology, related to but different from ActiveX control objects. Automation objects have the following distinguishing characteristics:

You can use Internet Explorer as an Automation server to provide additional Internet features to your control project in situations where you would rather not use the WebBrowser control. To see an example of how to do this:

  1. Start a new control project. This project will provide the user with a list of choices from which to obtain online help in an application; selecting a choice will launch an instance of Internet Explorer and display an online help topic.
  2. Give the UserControl the name OnlineHelp.
  3. Add a constituent list box control to the project. Give the list box the name lstURL.
  4. Double-click on the control designer to open its code window. In the control's Initialize event, populate the list box by adding the following code:
Private Sub UserControl_Initialize()
    lstURL.AddItem "General Help Topics"
    lstURL.AddItem "Printing"
    lstURL.AddItem "Saving Your Work"
End Sub
  1. Make the constituent list box resize to fit the dimensions of the UserControl by adding the following code to the UserControl's Resize event:
Private Sub UserControl_Resize()
    lstURL.Move 0, 0, ScaleWidth, ScaleHeight
End Sub
  1. Finally, place the following code in the constituent list box's DblClick event:
Private Sub lstURL_DblClick()
    Screen.MousePointer = vbHourglass

    Dim objExplorer As Object
    Set objExplorer = CreateObject("InternetExplorer.Application")

    
    Select Case lstURL.Text
        Case "General Help Topics"
        objExplorer.Navigate "http://www.microsoft.com/kb/"
        
        Case "Printing"
        objExplorer.Navigate "c:\work\print.html"
        
        Case "Saving Your Work"
        objExplorer.Navigate "c:\work\save.html"
        
        Case Else
        ' whoops! do nothing
        Exit Sub
        
    End Select
    
    ' Setting explicit Left and Top values
    ' ensures that IE will always appear in
    ' a full window even if the user has an
    ' existing instance minimized
    objExplorer.Left = 10
    objExplorer.Top = 10
    objExplorer.Visible = True
    Screen.MousePointer = vbArrow

End Sub

As you've probably surmised, this code launches Internet Explorer, tells it to navigate to a particular URL, then makes the application visible. (Most Automation servers are initially invisible by default.)

The code creates an instance of Internet Explorer by using the CreateObject function. Using the CreateObject function to create an instance of an Automation server is not unlike creating a control on a form in Visual Basic, except that Automation takes place in code; there is no visual component to it. Once you've created an instance of Internet Explorer (represented in this code by the object variable objExplorer), you can execute its methods (such as its Navigate method) and alter its properties (such as its Left, Top, and Visible properties).

Once the instance of Internet Explorer is visible, users are free to use Internet Explorer's user interface to do whatever they want, saving you a heap of trouble in the area of user-interface design and event handling.

To test the capabilities of the code you just entered, do the following:

  1. Close the code window and the control designer.
  2. Switch to the text EXE project form, Form1.
  3. Place an instance of the OnlineHelp control on Form1.
  4. Launch the project using the menu command Run, Start (or the function key F5).
  5. Double-click on the first item in the list. After a short delay, Internet Explorer should pop up, displaying the Microsoft Knowledge Base Web page.

If this were a real control project, you'd want to insert an error trap to handle the situation where a URL didn't exist or couldn't be opened by Internet Explorer. See Chapter 15 for more information on how to do this. A more elegant version of this control will be presented in Chapter 16, as you learn how to use Visual Basic's object-oriented programming features to create a working Internet Hotlist control project.

NOTE
There is an article on the Microsoft Web site that gives a code example of how to launch Internet Explorer from Visual Basic. The code example provided in the Microsoft article is incorrect, at least as of this writing. This article is in the Microsoft Knowledge Base; its article ID is Q160976. You can access the Microsoft Knowledge base on the Web at http://www.microsoft.com/kb/.

Summary

In this chapter, we explored a number of Internet-related topics, including downloading property values, sending Internet mail and controlling Microsoft Internet Explorer. It's good to get a handle on these technologies-even if you're not doing Internet stuff right now, chances are in the coming years you'll be called upon to do so.

In the next chapter, we'll jump into the infinitely sublime topic of handling errors and debugging your control projects.