ActiveX Documents in VB

by Peter G. Aitken

ActiveX gives you more than controls. An ActiveX document gives you the power to deliver VB-enabled content over the Web.

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, I will explain the fundamentals of ActiveX documents and what they can do for you.

.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 are primarily used for Web programming. Put simply, an ActiveX document is a Visual Basic application that is "published" to a Web site and then 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. They 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 a very poor choice. 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 .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, and so on. 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.

ActiveX documents run in a container. Three types of 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’m 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 Microsoft browser.

The amount of the ActiveX document that is visible depends—obviously—on the screen size of the container it’s 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 the local system. This requirement for support-file download is another reason why ActiveX documents are more suitable for specific Web-based applications and are 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, because ActiveX document technology is a "heavy hitter," it 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.

The User’s View

Using an ActiveX document is very simple. All that is required is to navigate to the corresponding .VBD file using a compatible Web browser. You can also link to a .VBD file from an 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 all subsequent, only step 4 is required, times a particular user navigates to an ActiveX document. 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, the new version will be downloaded the next time a user navigates to it.

The UserDocument Object

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 UserDocument objects. 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) in which the ActiveX document is running. 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, and so on. 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’re 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.

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).

Table 1. Some important UserDocuments events.

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 often 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 that contrasts with modal forms that 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.

Property Description
ContinuousScroll Set to 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 is released by the user.
HscrollSmallChange and 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.

Table 2. Some important UserDocuments properties.

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") is:

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 of 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 on the user’s machine.

How, then, can you navigate? 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’re 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.

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’s 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, compared with 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 requires little programming on your part.

To use the property bag, you need to know about a couple of UserDocument events. 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), 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 were 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 method 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])

Here, DataName is the name to be associated with the data, and Value is the data itself. DefaultValue is an optional default value to associate 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. 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 if you need to use it as a property. (The use of public properties in ActiveX documents will be covered below.)

Note that the property bag works properly only when you run a compiled project outside the Visual Basic environment. When you run an ActiveX document project 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 at this point, 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 that provides two boxes where the user can enter information. Another button 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, as shown in Figure 1. 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.

Figure 1. The first UserDocument.

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 UserDocument, 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, and then save the module.

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

Figure 2. Executing the ActiveX document in Internet Explorer.

The application is shown executing within Internet Explorer in Figure 2. 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 below.

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, as 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. v

Listing 1. GetPathFromFullFileName

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

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

We started looking at one of Visual Basic’s powerful Internet technologies, ActiveX documents. An ActiveX document is, in effect, a full-featured Visual Basic application that is deployed over the Internet and run inside a container, usually a Web browser. Despite the "document" part of its name, an ActiveX document is better described as a hybrid between an application and a document, containing both executable code and data. ActiveX document technology offers you the full power of the Visual Basic language and its visual interface design tools coupled with the capability for Web-based deployment and updating—something that is become more and more important these days.

There are tradeoffs, of course. The technology’s hefty download requirements and limited browser compatibility limit its usefulness primarily to intranets. Still, that leaves a huge number of places where ActiveX document technology may well be your best bet for Web development.

Container Differences

At present, ActiveX documents can be "run" in three different containers: Internet Explorer versions 3 and greater, Microsoft Office Binder versions 1 and later, and the Visual Basic development environment tool window. While ActiveX document applications are often written with a particular container in mind, the possibility exists for applications to be written for multiple containers. This is, I believe, an exciting possibility, particularly as more and different containers become available.

That said, it does complicate programming to some degree. While a container capable of hosting an ActiveX document must, of course, meet a minimum set of capabilities in order to do so, the different containers will differ unavoidably in various ways that are potentially relevant to an ActiveX document that is running in them. One example is navigation. In Internet Explorer, you navigate using the Hyperlink object’s NavigateTo method, whereas the Binder requires that you add a new Binder section, and the code is completely different. This means that your ActiveX document applications need to check which sort of container they’re running in. Even if your application is intended to run only in a particular container, you may want to have start-up code that checks the container and, if it is not the proper one, displays a message to the user and terminates.

To determine which container type an ActiveX document application is running in, use the TypeName function, which returns information about the variable passed as its argument. If the variable is a reference to the ActiveX document’s parent, obtained as UserDocument.Parent, then the function returns a string identifying the container, as shown in Table 3.

Container Return Values
Internet Explorer "IWebBrowserApp"
Binder "Section"
Visual Basic tool window "Window"

Table 3. Document container ID values.

At least, this is what the Microsoft documentation says is supposed to happen. I have noted, however, that when Internet Explorer 5 is the container, TypeName returns the string "IWebBrowser2." My guess is that if the return value includes the text "browser," then the container is some version of Internet Explorer. Then, if your ActiveX document project is intended to run only within a browser, you could put something like the following in each document’s Show event procedure:

If InStr(1, TypeName(UserDocument.Parent), "browser", vbTextCompare) = 0 Then
' Code here to display message and terminate application.
End If

Public Properties, Variables, Methods, and Procedures

A public property, variable, method, or procedure is one that is available throughout the entire ActiveX document project, and not just in the document where it is defined. Once you go beyond the basics with ActiveX documents, you’ll find that multiple-document projects are often the easiest way to accomplish your task, and these multiple documents often need to "talk" to each other. We’ll get to why they might need to talk to each other in a minute, but first the how. If you’re familiar with making items public in ActiveX controls, this will all seem very familiar to you.

Making public a procedure or a variable (in a code module) or a method or a property (in a UserDocument) is simply a matter of using the Public keyword in the definition or variable declaration. Public is the default for regular and property procedures, and for methods (which, of course, are just procedures under another name), so these code elements will always be public unless you explicitly make them private. Then, call them from other modules using the procedure name or, for a method, the standard object.method syntax. More important, as we’ll soon see, is creating public variables in code modules. By declaring a variable at the module level (outside any procedures), using the Public keyword results in a variable that is visible throughout the project.

When do public items in an ActiveX document project become available? Items—public procedures and variables—in a code module are always available. Things are a bit more complicated when it comes to public methods and properties of a UserDocument. Anything within a UserDocument becomes available when an instance of the document is created, which happens when the document is loaded into a container. Then, the items go out of scope—become "unavailable"—when that instance of the document is destroyed, which usually happens when it is unloaded by its container. I say "usually" because there are differences between containers in terms of when a document instance is actually destroyed. Because some containers do, in fact, destroy a document instance as soon as it is unloaded, you should write your code as if this is always the case.

Perhaps you can already see the potential for problems. The whole idea of public properties and methods in one document is to make them available to code in other documents, right? Thus, Doc1 might contain properties and methods that are accessed by code in Doc2. What happens if Doc2 runs before Doc1 is loaded, or after Doc1 has been unloaded and destroyed? Remember, in an ActiveX document project you do not have the same close control over when things are loaded and unloaded as you do in a regular Visual Basic application. Any ActiveX document can be navigated to independently, and your coding must take this possibility into account.

The best solution is to keep a separate, global reference for each document in the project. These references are created as global variables in the project’s code module. For example, suppose your project contains three documents. Then, at the module level in the project’s code module, you would place the following public variable declarations:

Public gDoc1 As Object
Public gDoc2 As Object
Public gDoc3 As Object

Then, within each document, you would add code to set the global reference to that document. For example, somewhere in the code in Doc1 would be:

Set gDoc1 = Me

Where exactly this code goes depends on the details of your project. If you always want the reference to the document saved in the global variable, then the code could go in the document’s Show or Terminate event procedure. If the reference needs to be set only under certain circumstances—say, when the user navigates away from this document to another specific document—then the code could be placed along with the navigation code. In either case, since a reference to the document has been saved in a global variable, the instance of the document will not be destroyed when it is unloaded from the container but will be available as long as the application is running. Hence, the document’s public properties and methods will remain available.

"But wait," you may be thinking, "What if Doc1 has not been loaded at all when another document’s code references it?" Good question—but the point of this technique is not to guarantee that an instance of Doc1 always exists, but to permit other components of the project to determine whether an instance exists and then take appropriate action. This is done simply by querying the value of the global variable—gDoc1 in this case. If its value is Nothing, then an instance does not exist and the code can take appropriate action—including creating an instance, if required. If, on the other hand, the value of gDoc1 is not Nothing, then an instance of the document exists and its public methods and properties can be used.

Here’s an example. Suppose you want your project’s various documents to have a consistent appearance, but also want to permit the user to set the foreground and background colors as desired. When Doc2 is loaded, you want it to use the same background and foreground color as Doc1, but only if Doc1 has already been loaded, which means that the user may have customized its colors. If Doc1 has not been loaded, then Doc2 should use the default foreground and background colors. The following code in Doc2’s Show event procedure does the trick.

Private Sub UserDocument_Show()
   If gDoc1 Is Nothing Then
      BackColor = DEFAULT_BACKCOLOR
      ForeColor = DEFAULT_FORECOLOR
   Else
      BackColor = gDoc1.BackColor
      ForeColor = gDoc1.ForeColor
   End If
End Sub

By using this same general technique, with variations to suit the occasion, you can make use of the power of a multi-document project without running into problems trying to reference a non-existent object. It is also possible to use global code module variables to pass data between one document and another. I have found this useful for small bits of data, such as maintaining application-wide flags. Larger chunks of data are better passed between documents using the file system.

Remember that good programming practice requires that you destroy object instances when they are no longer needed. If you are keeping global references, this is particularly important, because you cannot count on your document objects being destroyed automatically when unloaded. To avoid hogging system resources with no-longer-needed objects, while retaining objects you will refer to later, you may need to do a little bit of planning.

Viewports

What guarantee do you have that the container’s viewing area will be big enough to display your entire ActiveX document? None at all. To deal with this situation, container objects provide for scrolling to bring different parts of a too-big document into view. The "window" through which you view a part of the document is called the viewport. When the document is longer and/or wider than the viewport, the container automatically displays a vertical and/or horizontal scrollbar to permit the user to scroll. If the user changes the size of the container, the scroll bars appear and disappear as needed.

The automatic display of scroll bars by the container is controlled by the UserDocument’s ScrollBars property. The default setting is for both scroll bars (horizontal and vertical) to be displayed as needed. You can change this property to display only one, or no, scroll bar, but I cannot imagine a situation where this would make sense.

When the application is running, code in the ActiveX document can obtain information about the current viewport—its position relative to the UserDocument and its size. In effect, this permits your code to determine what parts of the document are visible, and what parts are not visible, at any given time. This information, in units of twips, is available from the following UserDocument properties:

ViewPortWidth
ViewPortHeight
ViewPortLeft
ViewPortTop

As with all default coordinate systems in Windows, the top left is the origin, with coordinates 0,0. What can you do with this information? Depending on the nature of your document, you may be able to resize it so it fits perfectly within the container viewport, freeing the user from the need to scroll to see all parts of it. If the document cannot be resized—perhaps it contains a collection of carefully placed controls—you can scroll the document under program control to bring a particular part of it into the viewport. This code shows an example. When the specified text box gets the focus, the viewport is shifted to move that text box to the upper left corner of the viewport.

Private Sub Text1_GotFocus()
  UserDocument.SetViewport Text1.Left, Text1.Top
End Sub

Note that this works only if the viewport is smaller than the document when the focus is moved.

Asynchronous Data Transfer

Asynchronous data transfer permits an ActiveX document to obtain data from a remote source, whether it be a file or a URL. Because the transfer is asynchronous, it happens in the background while your ActiveX document application is busy doing other things. When the transfer is complete, an event fires, notifying the program. Another event fires as the download progresses, giving the program access to the data as it arrives.

You use the UserDocument’s AsyncRead method to initiate an asynchronous data retrieval. The syntax is:

AsyncRead Target, AsyncType, PropertyName, AsyncReadOptions

Target is a string expression that specifies the data to retrieve. It can be a URL or a path to a file.

AsyncType specifies the format of the data being retrieved. Use VbAsyncTypeFile if the data is a file created by Visual Basic, VbAsyncTypePicture if the data is a Picture object, and VbAsyncTypeByteArray if the data type is unknown. (That is, if it is an arbitrary sequence of bytes with no assumptions as to its structure.)

PropertyName is an optional argument by which you assign an arbitrary name to the download. The name you assign has no effect other than to permit you to identify the download process if two or more downloads are ongoing at the same time.

AsyncReadOptions is an optional argument that specifies certain download options, mainly having to do with server versus locally cached copies of the data. Possible settings are explained in Table 4. The identifiers listed are constants, with their numeric values in parentheses after the identifiers.

Constant (Value) Description
VbAsyncReadSynchronousDownload (1) The download is done synchronously, which means that execution does not return from the call to AsyncRead until the download is complete.
VbAsyncReadOfflineOperation (8) The locally cached copy (if present) is used.
VbAsyncReadForceUpdate (16) The remote copy is retrieved regardless of whether a local copy is available.
VbAsyncReadResynchronize (512) The remote copy is retrieved only if it is newer than the locally cached copy.
VbAsyncReadGetFromCacheIfNetFail (524288) Use the locally cached copy if there is a network problem; otherwise retrieve the remote copy.

Table 4. AsyncRead's AsyncReadOptions argument values.

What exactly happens when you call the AsyncRead method? Errors can occur with this method, and they occur synchronously, which means that you should have error trapping enabled when you use this method. Absent an error, the data transfer begins and, assuming you did not specify a synchronous download with the VbAsyncReadSynchronousDownload option, program execution continues with the statements following the call to AsyncRead as the download proceeds in the background. Two events fire in response to the download: AsyncReadProgress as the download progresses, and AsyncReadComplete when it is finished. When the download is complete, the data is available in a disk file for your program to use as needed.

Both of these event procedures receive a single argument, of type AsyncProperty. This is an object whose properties provide information about the download at the time the event fired. These properties are explained in Table 5. The status codes returned in the StatusCode property are listed in Table 6. As with Table 4, the number in parentheses is the constant’s numeric value.

Property Description
AsynchType The type of data being downloaded. Possible values are the same as for the AsyncRead method's AsyncType argument, as explained above.
BytesMax An estimate of the total number of bytes to be downloaded, as a type Long.
BytesRead The number of bytes read so far, as a type Long.
PropertyName The PropertyName argument passed to the AsyncRead method, uniquely identifying the download.
Status A string describing the current status of the download.
StatusCode A numerical code giving the current status of the download operation. See Table 6.
Target The target argument passed to the AsyncRead method.
Value The name of the local file where the downloaded data is placed.

Table 5. Properties of the AsyncProperty object.

 

Constant (Value) Meaning
VbAsyncStatusCodeError (0) An error has occurred. The Value property contains information about the error.
VbAsyncStatusCodeFindingResource (1) The target is being located.
VbAsyncStatusCodeConnecting (2) A connection is being established with the target.
VbAsyncStatusCodeRedirecting (3) The method has been redirected to another location.
VbAsyncStatusCodeBeginDownloadData (4) The download has started.
VbAsyncStatusCodeUsingCachedCopy (10) Data is being obtained from a local cached copy of the target.
VbAsyncStatusCodeSendingRequest (11) The method is requesting the target.
VbAsyncStatusCodeMIMNETypeAvailable (13) The MIME type of the target is available, and is specified in the Status property.
VbAsyncStatusCodeCacheFileNameAvailable (14) The filename of the local cached copy of the target is available in the Status property.
VbAsyncStatusCodeBeginSynchOperation (15) The download is being done synchronously.
VbAsyncStatusCodeEndSynchOperation (16) A synchronous download has completed.

Table 6. Status codes for the AsyncProperty object.

With these two events, you can probably see the basic structure of the code required to perform a data transfer. The basic outline of the required steps is this:

  1. Call the AsyncRead method, passing the appropriate arguments for the data being retrieved. Be sure to trap errors.
  2. In the AsyncReadProgress event procedure, place code to examine the properties of the AsyncProperties object. It is optional to keep the user informed of the progress of the download, but it is strongly advised to perform error checking here, keeping a lookout for a value of VbAsyncStatusCodeError in the StatusCode property.
  3. In the AsyncReadComplete event procedure, place code to inform the user that the download is complete and to take whatever actions are required with the downloaded data.

You can see how the ability to asynchronously read data from a URL could be a very powerful tool. But why would you want to use AsyncRead to read data from a remote file on a local network? Visual Basic’s regular file access statements are suitable for that task. The answer lies in the asynchronous nature of the transfer. Even local networks can experience congestion and delays, and since the regular file access statements operate synchronously, your program will "hang" while a slow file access operation is in progress. With AsynchRead you avoid this potential problem.

Menus in ActiveX Documents

ActiveX documents begin to seem even more like regular Visual Basic applications when you realize that they can have their own menus. When you are working on a UserDocument in the Visual Basic development environment, you use the menu editor to create you menu, just as for a regular Visual Basic form. When the document is loaded into its container, its menu is combined with the container’s own menu, a process known as menu negotiation.

When you are using the menu editor, you’ll see a NegotiatePosition property associated with each menu item. This property is relevant only for top-level menu items, and cannot be changed from its default setting of "0-None" for sub-items. When applied to a top-level menu item, the possible settings and their effect are shown in Table 7.

Setting Effect
0 - None The menu does not display in the container.
1 - Left The menu is displayed at the left on the container's menu bar.
2 - Middle The menu is displayed in the middle of the container's menu bar.
3 - Right The menu is displayed at the right on the container's menu bar.

Table 7. Settings for the NegotiatePosition property.

When the document is displayed in the container, the final menu arrangement depends on both the Caption and the NegotiatePosition properties of the document’s top-level menu items. There are three possibilities:

  • If the container does not have a top level menu item with the same caption, then the document menu will have its own position on the menu bar, at the position specified by the NegotiatePosition property.
  • If the container does have a top level menu item with the same caption, and it is at the same position as the document menu item’s NegotiatePosition property, then the document menu item will be merged with the container’s menu item.
  • If the container does have a top level menu item with the same caption, and it is not at the same position as the document menu item’s NegotiatePosition property, then the document menu will have its own position on the menu bar, at the position specified by the NegotiatePosition property.

This may seem a bit confusing, so let’s look at an example. If your document has a top level menu with the caption Help, with NegotiatePosition set to "3—Right", then it conflicts with the Internet Explorer menu and therefore will display as a sub-item on Internet Explorer’s Help menu (with the caption "XXXXHelp", where XXXX is the name of the UserDocument).

If, however, the NegotiatePosition property is set to "2—Middle", there is no conflict, because Internet Explorer does not have a Help menu item in the middle of the menu bar. In this case, the document’s Help menu will display separately on the menu bar.

Depending on the container, it may also be possible to manipulate the container’s menu, although I’m not able to cover it in this column. This provides you with the possibility of completely customizing the container menu so that the user sees only those menu commands that you want them to have access to—always a good idea to keep careless users from getting themselves into trouble!

Converting Existing Applications to ActiveX Documents

In the long-standing Visual Basic tradition of making your life easier, there’s an automated way for you to convert existing Visual Basic applications to ActiveX document applications. After all, since an ActiveX document is, in effect, a Visual Basic application in a different suit of clothes, there are many situations where converting an existing application to an ActiveX document makes a lot of sense. The tool is called the ActiveX Document Migration Wizard, and it is an add-in that is accessed from Visual Basic’s Add-Ins menu. The wizard does not change the existing application, but creates a new ActiveX document project based on it. Some of the things it does are these:

  • Copies properties, controls, and code from forms to UserDocuments.
  • Comments out illegal code, such as the End statement.
  • Removes any OLE container controls and embedded objects (such as Excel spreadsheets), which are not supported in ActiveX documents.

For event procedures, where an exact counterpart exists, the wizard copies the code and changes the procedure name. For example, Form_Click is changed to UserDocument_Click. Where there is not an exact counterpart, the event procedure is copied with its name unchanged. Thus, the Form_Load event procedure is copied with its name unchanged and can be called as a general procedure, if needed.

Note that the process of migrating a standard Visual Basic application to an ActiveX document project is not completely automatic—after the wizard does its work, you’ll still need to work on the project a bit. However the wizard certainly saves a lot of time.

The Bottom Line

With ActiveX documents, I think we get a glimpse of the future of application development. Even more, we get a glimpse of the direction that computing in general is headed. The Internet has only begun to change things, and as the "wired world" continues to evolve, the distinctions between local and remote, here and there, will blur even further. Developers will have more choices, of course, but equally important is that they will be freed from other choices because they will no longer matter. The dichotomy of whether an application is deployed locally or remotely, and whether it executes locally or remotely, is already losing some of its meaning. ActiveX documents are a reflection of these changes, and it’s a technology that you can put to work today. v


During daylight hours, Peter is a research scientist at Duke University Medical Center in Durham, North Carolina. Comments and suggestions may be sent to him at paitken@acpub.duke.edu

Copyright © 1999, 2000 The Coriolis Group, LLC. All rights reserved.
©Aivosto Oy -