Basically Visual

November/December 1999

by Peter G. Aitken (c)1999

Originally published in Visual Developer magazine


ActiveX Documents in Visual Basic

Introduction

One of the fundamental technologies behind Visual Basic development, and indeed behind Windows applications in general, is ActiveX. Most Windows developers know about ActiveX, but when you ask them it seems that all they really know about are ActiveX controls. There can be no doubt that ActiveX controls are an essential tool for Windows programming, but they are only part of the picture. For a Visual Basic programmer, there are other powerful ways to use ActiveX technology. In particular, a type of Visual Basic project called an ActiveX document provides some development capabilities that you may find very useful. In this column and the next I will explain the fundamentals of what ActiveX documents are and what they can do for you.

 ActiveX documents are primarily used for web programming. Put simply, an ActiveX document is a Visual Basic application that is "published" to a web site, then is downloaded and executed in the user's web browser. An ActiveX document is not a web page in the usual sense, as it involves no HTML or script. To the end user, however, this distinction is moot as all they really care about is what they see and do on-screen and usually do not give a tinker's damn about the underlying technology. If you have worked with Visual Basic to any extent you know its incredible power, and the ability to deploy full-fledged Visual Basic programs as web-based applications should be a tempting inducement.

 To be honest, I find the "document" part of the name ActiveX document is a very poor choice, as we tend to associate this word with data, or content, and not with functionality. A Microsoft Word document, for example, contains the content while the Microsoft Word program contains the functionality. In actuality an ActiveX document is better thought of as a hybrid between a document and an application. An ActiveX document project is made up of a document that contains the data, and a server, or application, that provides the functionality. After compilation, the document is contained in a Visual Basic Document file (.VBD) and the server is contained in either an .EXE or a .DLL file. During development the project is in a .DOB file which is a plain text file containing the definitions of the project's controls, source code, etc. If an ActiveX document project contains graphical elements that cannot be stored in text format, they will be kept in a .DOX file. The .DOB and .DOX files in an ActiveX document project are parallel to the .FRM and .FRX files of a regular Visual Basic executable project.

EXE versus DLL. Visual Basic offers you the choice of creating your ActiveX document project as an EXE or a DLL. The differences between these lie in speed and safety. An EXE runs out of process with respect to its container (the browser), which means that it has its own memory space. Communication between separate processes is slower, but because the EXE runs in its own memory space it can crash without causing the container to crash. A DLL runs in process, sharing the same memory space as the container. Intra-process communication is a lot faster, but a bug in the DLL can crash the container. Functionally there is no difference between EXE and DLL, you select one or the other depending on whether utmost speed or crash protection is more important to you.

 ActiveX documents run in a container. Three containers are available: a web browser,  Microsoft Office Binder, and the Visual Basic development environment tool window. While the last two containers offer some interesting development possibilities, it is on the web where ActiveX documents are most often used. At present, I believe that only Internet Explorer (versions 3 and later) offer the ActiveX support required  to run ActiveX documents. I know that some degree of ActiveX support is available for Netscape's browser, but whether this is sufficient to run ActiveX documents I am not sure. This browser limitation means that ActiveX document projects are best suited for deployment on an intranet where you know that all potential users have the required browser.

The amount of the ActiveX document that is visible depends obviously on the size of the container it is running in. If the container is smaller than the document, horizontal and/or vertical scroll bars are automatically displayed by the container to permit other areas of the document to be scrolled into view.

What are the logistics of deploying an ActiveX document? A deployment consists of one .VBD file for each document in the project plus a compressed CAB file containing the compiled DLL or EXE file. The CAB file may also contain the Visual Basic runtime and support files, or you can specify that these be downloaded directly from Microsoft's site. This latter option reduces the size of your deployment but does not remove the requirement that the Visual Basic runtime and support files must be downloaded - only once, however (unless the user already has these files on their system). This requirement for support file download is another reason why ActiveX documents are more suitable for specific web-based applications and less suitable for a general "public" web page.

When, then, should you choose ActiveX document technology over other web development approaches? It's not a cut and dried answer, as there is so much overlap in the capabilities of various web development tools. Generally speaking, ActiveX document technology is a "heavy hitter" and should be reserved for those situations where its additional capabilities justify its greater overhead. If you can accomplish your goal with DHTML or an ActiveX control then by all means you should - you do not need an ActiveX document.

Using an ActiveX document is very simple. All that is required is to navigate to the corresponding .VBD file using a compatible browser. You can also link to a .VBD file from a HTML page using the standard hyperlink tag:

<a href="sales.vbd">Click to open Sales</a>

When a user navigates to an ActiveX document for the first time, here's what happens:

1. The CAB file is downloaded.
2. The EXE or DLL that contains the server is extracted from the CAB file and installed on the system.
3. If required, the Visual Basic runtime and support files are installed (either from the CAB or from the Microsoft download site).
4. The VBD file is either downloaded to the local computer or, more often, opened from the remote location.

The second and subsequent times a user navigates to an ActiveX document, only step 4 is required. Hence, the download overhead associated with this technology is a one-time event. If you deploy an updated version of the application to the server, then the new version will be downloaded the next time a user navigates to it.

The UserDocument

The UserDocument object is at the heart of any ActiveX document project, just like the Form object is the basis for standard EXE projects and the UserControl object is the basis for ActiveX control projects. In fact, much (but not all) of what you know about Visual Basic forms also applies to UserDocuments. This is in keeping with the container model that is so prevalent in Windows programming. A Visual Basic form, for example, serves as a container for the controls and code that you put in it. The form provides the wrapper, or interface, between its contents and the next higher level of "containerness," in this case the Windows operating system. Likewise a UserDocument is a container for controls and code that you add, and provides an interface between these elements and the container (a web browser, for example) that the ActiveX document is running in. The same analogy can be extended to the UserControl object that serves as the basis for creating ActiveX controls in Visual Basic.

When you start a new ActiveX document project, it contains a single UserDocument. You add controls and code to the UserDocument in the usual manner. A project can contain multiple UserDocuments, and can also contain a code module where you can put shared procedures, type declarations, global variables, etc. A UserDocument's events are important in putting an application together, and it is here that you'll find some differences from the standard Visual Basic form. A UserDocument detects many of the events you are used to working with on forms, such as KeyDown, MouseMove, OLEDragOver, and Click. Missing, however, are Activate, Deactivate, the various DDE-related LinkXXXX events, Load, QueryUnload, and Unload. Instead the UserDocument detects other events, including those listed in Table 1, that are appropriate for the way it is used.

Table 1. Some important UserDocument events.

Event

Occurs

EnterFocus

When the UserDocument or a constituent control gets the focus.

ExitFocus

When the UserDocument or a constituent control loses the focus.

Hide

When the user navigates away from the UserDocument.

Resize

When the container is resized.

Scroll

When one of the container's scrollbars is moved.

Show

When the user navigates to the UserDocument.

Terminate

Just before the document is destroyed (closed).

 There are a few other UserDocument events that we will deal with as needed.

 An ActiveX document project can also contain standard Visual Basic forms. Unlike UserDocuments, forms are not displayed in the container but rather as stand-alone "pop-up" windows. In most cases an ActiveX document application is designed so the main part of the application runs in the container, and therefore is programmed as one or more UserDocuments. Regular forms are used more as dialog boxes. However this approach is not required, and the potential combination of UserDocuments and forms provides a great deal of flexibility when designing your application. Be aware, however, that some containers (including Internet Explorer) do not permit a modeless form to be displayed by a DLL. A modeless form, you may recall, is one that can be left open when you switch to another window in the application, and contrasts with modal forms which must be explicitly closed before you can make another application window active. To use modeless forms in an ActiveX document application, therefore, you will have to deploy it as an EXE.

The UserDocument object also has a set of properties, of course. There is some overlap with Form properties, but as with events there are also some important differences. Table 2 describes the most important UserDocument properties.

Table 2. Some important UserDocument properties.

Property

Description

ContinuousScroll

True if you want the UserDocument to scroll smoothly within its container as the user moves the scroll bar thumb. If False the UserDocument is redrawn only when the thumb us released by the user.

HScrollSmallChange

VScrollSmallChange

The distance the UserDocument moves when the user clicks an arrow on the container's scroll bars.

HyperLink

Returns a reference to a HyperLink object (but only if supported by the container).

Parent

Returns a reference to the UserDocument's container object.

Picture

Specifies an image (BMP, JPG, or GIF file) to display as the UserDocument's background.

Of these properties only HyperLink requires some explanation. While provided by the container, HyperLink is a property of the UserDocument and provides a way to navigate to a different URL. The most important method of the HyperLink object is NavigateTo. The syntax, assuming the UserControl is named UC1, as::

UC1.Hyperlink.NavigateTo(Target [, Location [, FrameName]])

Target specifies the destination location. It can be a URL or a local document. Location specifies the location (bookmark) in the target to display. FrameName specifies the target frame to navigate to. If either of the last two arguments are omitted, the target's defaults are used. Here are two examples of using NavigateTo, one which navigates to a remote URL and another which opens a local file (an ActiveX document):

UC1.NavigateTo "http://www.microsoft.com"
UC1.NavigateTo "c:\VBprojects\TestPage.vbd"

The HyperLink object also has GoForward and GoBack methods that navigate forward and backward in the history list. These methods take no arguments. If the container does not maintain a history list, or if the list is empty, these methods generate an error, so you must use error trapping.

There is an additional consideration when navigating between ActiveX documents. The NavigateTo method requires the full path to the VBD file you are going to. During project development you know where the VBD files are located: they are placed in the Visual Basic folder when you run the project from within the Visual Basic development environment, and are placed in the project folder when you compile the project. Once the project is deployed on the Internet, however, you cannot predict where the downloaded files will end up being placed on the user's machine. How then can you navigate? The solution is based on the fact that for a multiple document ActiveX application, all the VBD files will be downloaded to the same folder. By querying the path to the initial document (which loads automatically) you can determine the path to the other documents. This initial document path is obtained from the browser's LocationURL property, which returns the fully qualified filename of the currently loaded document. Strip off the filename part to obtain the path information, and you are all set. To simplify this task you can use the function GetPathFromFullFileName which is presented in Listing 1. This function is passed a fully qualified filename and returns the path portion. Its operation is straightforward - it looks for the last / or \ in the fully qualified file name; anything up to and including that character is the path.

Listing 1. The function GetPathFromFullFileName returns the path portion of a fully qualified file name.

Option Explicit

Public Function GetPathFromFullFileName(FullFileName As String) _
        As String

' Passed a fully qualified filename, removes
' the filename part and returns the path information.
' Returns a blank string if there is no path information.

 Dim idx As Integer

 ' Strip any spaces.
FullFileName = Trim(FullFileName)

' Check for empty argument.
If Len(FullFileName) = 0 Then
   
GetPathFromFullFileName = ""
   
Exit Function
End If

' Look for last / or \.
For idx = Len(FullFileName) To 1 Step -1
   
If Mid$(FullFileName, idx, 1) = "\" Or _
      
Mid$(FullFileName, idx, 1) = "/" Then Exit For
Next idx

If idx = 1 Then
   
GetPathFromFullFileName = ""
Else
   
GetPathFromFullFileName = Left$(FullFileName, idx)
End If

End Function

Given this function, here's how to navigate to UserDocument2.vbd:

Dim p As String

p = GetPathFromFullFileName(UserDocument.parent.LocationURL)  Hyperlink.NavigateTo p & "UserDocument2.vbd"

Saving Data

Most ActiveX document applications require some way to save data between sessions - specifically, data that has been entered by the user. An ActiveX document has all of Visual Basic's file reading and writing commands available, but it is not recommended that you use them. Rather, data should be saved in a property bag. As the name implies, a property bag (represented by the PropertyBag object) is intended for storing properties, and that's exactly what it is used for when creating an ActiveX control. In an ActiveX document, by treating your user data as properties, you can also use the PropertyBag object to store the data. There are several advantages to this approach as compared with using the standard file reading and writing commands:

- Keeping all data in a single location (the property bag) simplifies the application.

- General principles of Internet programming require minimal access to the user's file system.

- Writing to and reading from the property bag is handled by the container object, and required little programming on your part.

To use the property bag there are a couple of UserDocument events you need to know about. When an ActiveX document is first opened in its container the first event to fire is always Initialize. The second event depends on whether there are saved properties for this document. If there are none (and this is determined automatically by the container) then the second event is InitProperties. If there are saved properties, ReadProperties fires instead. You put code in InitProperties to set initial default values for the properties (data); these values will be in effect if there are no saved properties. You put code in ReadProperties to read property values from the PropertyBag object when they are available.

The WriteProperties event procedure is called when the application is closing, but only if the container has been notified that one or more properties have been changed by a call to the PropertyChanged method. You put code in the WriteProperties event procedure to actually write the properties to the property bag. The PropertyChanged is usually passed the name of the changed property, but this is not really necessary. Which properties are saved is determined by the code in the WriteProperties event procedure. Calling PropertyChanged, whether once or a hundred times, serves only to tell the container that WriteProperties needs to be called when the application closes. The PropertyBag object itself is automatically created and handled by the container, and a reference to it is passed to the ReadProperties and WriteProperties event procedures.

To write data to the PropertyBag object, use the WriteProperty method:

PropBag.WriteProperty(DataName, Value [, DefaultValue])

DataName is the name to be associated with the data, and Value is the data itself. DefaultValue is an optional default value to associated with this data item. Properties are read from the property bag using the object's ReadProperty method. Its syntax is:

PropBag.ReadProperty(DataName [, DefaultValue])

DataName is the name associated with the data when it was saved in the property bag, and DefaultValue is the value to return if the specified data item is not found in the property bag.

To save data in the PropertyBag object, the data does not have to actually be a property - in other words, you do not have to create Property Let and Property Get procedures for it. Make the item a property only of you need to use it as a property. The use of public properties in ActiveX documents will be covered in the next column.

Note that the property bag works properly only when you are running a compiled project outside of the Visual Basic environment. When you run an ActiveX document project from within the Visual Basic environment, the property bag persists data only as long as the browser remains open.

A Demonstration

There are quite a few more details left to cover with regard to ActiveX documents, but we have reached the point where you can put together a useful if not terribly exciting demonstration. This ActiveX document program will show you the nuts and bolts of creating a two-document application, navigating between documents, and persisting user data. The application has a main document which provides two boxes where the user can enter information. There is also a button which navigates to the second UserDocument. This second document contains nothing but a button for returning to the first document. You'll see that user data entered in the text boxes is saved between visits to the first document.

To begin, fire up Visual Basic and create a new ActiveX Document DLL project. Open the UserDocument and place one large and one small Text Box on it. For the large box change the Name property to txtUserComment and the Multiline property to True. Change the small box's Name property to txtUsername.  Add two labels to identify the text boxes. Finally add a Command Button and change its Name property to cmdNext and its Caption to Next. Save the UserDocument under its default name of UserDocument1.

 Select Add User Document from Visual Basic's Project menu to add a second UserDocument to the project. Place a single Command Button on this User Document, with the Name property set to cmdGoBack and the Caption set to Go Back. Save this item under its default name as well.

Next, select Add Module from the project menu to add a code module to the project. Add the code from listing 1 to this module, then save the module.

The final steps to completing this project are to add the required code to the two UserDocuments. The code for UserDocument1 is given in Listing 2, and the code for UserDocument2 is in Listing 3.

Listing 2. Code in UserDocument1.

Option Explicit

Private Sub UserDocument_InitProperties()

' Initialize the document's two data items.
txtUserName.Text = "Enter your name"
UserComment = "Enter your comments"

End Sub

Private Sub cmdNext_Click()

Dim path As String

Path = GetPathFromFullFileName(UserDocument.Parent.LocationURL)
Hyperlink.NavigateTo path & "UserDocument2.vbd"

End Sub

Private Sub txtUserName_Change()

' Tell the container than a property has changed.
PropertyChanged "UserName"

End Sub

Public Property Get UserComment() As Variant

UserComment = txtUserComment.Text

End Property

Public Property Let UserComment(ByVal vNewValue As Variant)

txtUserComment.Text = vNewValue

End Property

 

Private Sub txtUserComment_Change()

'Tell the container that a property has been changed.
PropertyChanged "UserComment"

End Sub

Private Sub UserDocument_ReadProperties(PropBag As PropertyBag)

' Read the document's two data items from the property bag.
txtUserName.Text = PropBag.ReadProperty("UserName", "")
UserComment = PropBag.ReadProperty("UserComment", "")

End Sub

Private Sub UserDocument_WriteProperties(PropBag As PropertyBag)

' Write the document's two data items to the property bag.
PropBag.WriteProperty "UserComment", UserComment
PropBag.WriteProperty "UserName", txtUserName.Text

End Sub

 

Listing 3. Code in UserDocument2.

Private Sub cmdGoBack_Click()

Hyperlink.GoBack

End Sub

Note how the two data items were treated differently. The user comment is defined as a public property, with the corresponding Property Let and Property Let procedures. In contrast the user name is not a property at all, with no Get and Let procedures. Yet, as you can see, both can be persisted in the property bag. There are differences, however, in that the user comment will be available outside the document as a public property, as you'll see in the next column.

This application is deceptively simple - both simple to create and simple in appearance - but this belies the potential power of an ActiveX document application. On the one hand you have the power of Visual Basic to create sophisticated, complex applications. On the other hand you have the flexibility and interactivity of the Internet. It's a dynamite combination, I hope you'll agree. And you have only seen some of the tricks an ActiveX document can do! Tune in next issue for more cool stuff.