Microsoft Visual Basic ActiveX Document Objects: Bring your Applications to the World Wide Web (the Win32 part)

Microsoft® Visual Basic® programming system version 5.0 promises to bring the same ease to ActiveX component creation that previous versions have brought to standard Microsoft Windows® operating system graphical user interface (GUI) application programming. ActiveX™ document object creation is an important part of Visual Basic’s component feature set allowing you to create fully functional Visual Basic programs that run inside Microsoft Internet Explorer (IE) version 3.0. This paper describes ActiveX document object basics, creating and debugging ActiveX document objects, switching from form-based to document object-based applications, ActiveX document features, and some background on the roots of the ActiveX document technology. The focus is on information you won’t find in the documentation. I reference several samples you will find on your VBITS CD-ROM.

What’s in a Name

The terminology includes DocObj, OLE Document Object, ActiveX Document, and UserDocument. The document in ActiveX Documents is somewhat of a misnomer and at the end of this paper I go into more detail about why we have these unfortunate terms. The ActiveX Document technology consists of three components:

I will sometimes refer to the ActiveX Document Object as an AX DocObj, and Visual Basic AX DocObjs as UserDocuments. Visual Basic 5.0 AXDocObjs are applications created from components, not from documents . A better name would be ActiveX applets or ActiveX applications. AX DocObjs and ActiveX controls are a form of OLE embedding. Like ActiveX controls, they require a container; expose properties, methods, and events through a public interface; follow the rules of COM; and have the same lifetime issues as controls and automation servers. Unlike ActiveX controls, AX DocObjs fill the entire frame area (client window) of the host and don’t share the frame with other objects. The AX DocObj’s menus are also merged with the container’s menus. Since the ActiveX Document fills the frame and merges menus, it appears to replace the application. Some of the host’s user interface, such as IE’s navigation buttons and address field, is still available.

Creating a Visual Basic 5.0 ActiveX Document Object

With Visual Basic, you can create the ActiveX Document Object component of an ActiveX Document-based solution. The base class is called UserDocument. UserDocuments consist of the GUI designer, code window, base class, and a document file. Creating an ActiveX Document is as easy as starting Visual Basic and selecting the ActiveX Document EXE template. You get a project with one project item called UserDocument1. The project item displays a GUI designer that looks and behaves just like the GUI designer for a form. If you double-click the designer the code window appears. It’s a lot like creating a form. If you click Run (or press F5), the designer disappears and nothing happens, which is not like creating a form. The problem is that you need a container to host your UserDocument. Start Internet Explorer, type C:\vb5\userdocument1.vbd in the address field and your boring, gray UserDocument will appear. Close IE and get back to design mode. In the design environment you’ll notice you have the full power of Visual Basic’s GUI editor and supercharged code window. The same applies to run mode where you have the full use of the debugger even when your AX DocObj is hosted inside IE. That’s pretty powerful compared to the “designed with Notepad” technology of hypertext markup language (HTML) plus script.

The UserDocument Base Class

UserDocument is the name of the base class for all Visual Basic-created AX DocObjs, just like Form is the base class for forms. When you add a new UserDocument to your project it is called UserDocument1 because it has all the capabilities of the base class plus whatever changes you make in the code window and on the GUI designer. The base class provides lots of useful methods, properties, and events, many of them similar to those found on Forms and UserControls (the base class for creating ActiveX controls). The base class also provides all the OLE code needed to make your UserDocument run inside the container. In the UserDocument’s code module you can refer to properties of the base class using the UserDocument keyword. The UserDocument specifier is optional but it can make your code clearer. The UserDocument keyword includes the private interface of the UserDocument, so you can’t use this to pass references outside of your UserDocument. Instead, the Me keyword will return a reference of the public interface of the UserDocument.

The .vbd File

When you switch to run mode or compile your UserDocument project, Visual Basic will create a .vbd file for each UserDocument in the project. In run mode the .vbd file is put in the same directory as VB5.exe; when compiling it goes in the same directory as the dynamic-link library (DLL) and executable files. Initially, the .vbd file simply contains the CLSID of the UserDocument. The CLSID is a 128-bit number that uniquely identifies your UserDocument. Every time you compile, a new one is assigned. If you want the CLSID to remain fixed, then set the binary compatibility in the Project…, Properties dialog box under the Component tab. Visual Basic will automatically register your UserDocument when going into run mode, compiling, or during component download. When the UserDocument is registered an entry is made in the system registry under the CLSID that gives the location on disk of the UserDocument (the path to the DLL and executable files). When you type the path to the .vbd file in IE’s address field you are really telling IE to find the UserDocument server that can read this file. Using the CLSID, IE is able to find the correct server. If there is any data in the .vbd file, it is passed to the UserDocument during loading through the ReadProperties event. Since IE only knows how to load objects from a persisted state, the .vbd file is required to get your UserDocument loaded into IE.

Take HTML pages, for example. The HTML file is the persisted state of the HTML image you see on the screen. The IE HTML-rendering engine is in fact a DocObj called MSHTML.dll. However, HTML files are not DocFiles. IE uses the file extension to determine that it needs to load MSHTML.DLL. If you change the extension of DocFiles, IE still knows which server to load (try taking a Word document Sample.doc and renaming it Sample.xxx). Another important point is that you can have many .vbd files for a single UserDocument, just like there are many Word documents for a single server Winword.exe. This can be useful if you want to push data from the server onto the client. Just update the .vbd file on the server or create a copy, and the new .vbd file, but not the UserDocument DLL and executable files, will be downloaded.

Internet Features

I won’t go into great detail here because you can get this information from Visual Basic Books Online. The UserDocument base class contains a HyperLink object which allows you to navigate to other UserDocuments and anything else that can be displayed in IE (like an HTML page). There is also an AsyncRead method and AsyncReadComplete event. Combined they allow you to asynchronously download data over the Internet using http. You can choose to receive the data in a Byte Array, a Picture Object, or have it saved directly to disk in which case you get the filename. AsyncRead is not only useful to download data from a file on the server but you can also use it with Internet Information Server (IIS) 3.0 Active Server Pages (ASP files). For example, you can put data in the uniform resource locator (URL) which is passed to the server, the server side script reads that data and executes some script code (such as a server side database query), and then returns the results back to the client machine. The samples on your VBITS CD-ROM will help you better understand these features: Hyperlink, StockDemo (AsyncRead), and AsyncReadASP.

Scrolling and the Viewport

A UserDocument must conform to the dimensions of the container’s frame. The visible region of your UserDocument is called the Viewport. You could lay out the controls on your UserDocument each time the Viewport size changed but at some point the Viewport would be too small and your UI would look cramped and jumbled. Fortunately, UserDocuments have automatic scroll bar support. You specify the minimum width and minimum height, and if the Viewport becomes smaller than those dimensions the appropriate scroll bars are displayed and your UserDocument’s size remains fixed. Only the region intersecting with the Viewport is visible. You can also programmatically scroll the UserDocument to simulate internal hyperlinks. See the scroll sample for an idea of how you can take advantage of this feature.

Multiple UserDocument Programming Strategies

Just like form-based applications use multiple forms, you will want the same ability in your UserDocument projects. I’ll describe the differences and two strategies to make multiple UserDocument programs work. Don’t be discouraged by the confusing nature of the first strategy because the second strategy is much better. When Visual Basic programmers have two forms in their project and reference Form2 from Form1 they rarely stop to think where the variables Form1 and Form2 came from. Visual Basic predeclares these variables. If Form2 is not loaded and you access Form2.BackColor, then Form2 is automatically loaded for you. Furthermore, from Form1’s code window you can reference a subcontrol on Form2 (i.e., Form2.Text1.Text). All predeclared Form identifiers and all subcontrols and the Form’s base class are public to the entire project. Visual Basic UserDocuments don’t have predeclared global identifiers, and all the subcontrols and the UserDocument base class are private. So how can your UserDocument communicate with other UserDocuments and Forms? First, you need to expose the data through the UserDocument’s Public Interface. For example, suppose your UserDocument has a TextBox on it, and you want other project items to get and set the text. Simply define a public property Text like this:

Public Property Let Text(szNewValue As String)
    Text1.Text = szNewValue
End Property

Public Property Get Text() As String
    Text = Text1.Text
End Property

Now you need to get a reference to that UserDocument, but there is no predeclared identifier. There are two strategies you can take here. The first (more difficult) strategy is to use a global variable declared in a code module. For example:

‘In the code module Globals.bas
Dim g_doc1 As UserDocument1

‘Then somewhere in UserDocument1’s code window
Set g_doc1 = Me

The question is, where do you set the global variable? You might think that you could set it in the Initialize event of UserDocument1 and be done with it. But the variable g_doc1 is a reference to UserDocument1, which means UserDocument1 will never unload until you set g_doc1 to Nothing. Furthermore, g_doc1 is not guaranteed to point to the same instance of UserDocument1 that is visible in the container. IE, for example, has a cache of four objects. When it loads a fifth object it releases its reference to the first object. If the user navigates back to the first object IE will load a brand new object, even though g_doc1 still points to a valid UserDocument. It’s obvious that good management of the global variable is important. You need to release the reference as soon as you are done with it and then set it again the next time you need it. For example, you might set the global variable before you hyperlink to another UserDocument, extract the data, and then clear the reference:

‘In UserDocument1
Set g_doc1 = Me
Hyperlink.NavigateTo “c:\vb5\userdocument2.vbd”

‘In UserDocument2 Show event
If (Not (g_doc1 Is Nothing)) Then
   Text2.Text = g_doc1.Text
   Set g_doc1 = Nothing
End If

Or if you need to communicate with a Modal Form, you could use:

‘In UserDocument1
Set g_doc1 = Me
Form1.Show
Set g_doc1 = Nothing

‘In Form1_Load
Form1.Text1 = g_doc1.Text

But if the user navigates using IE’s Forward and Back buttons, since your code is not causing the navigation, how do you set the global variable? Here you must use the Show and Hide events:

‘In UserDocument1 Hide event
Set g_doc1 = Me

‘In UserDocument2 Show event (same as above)
If (Not (g_doc1 Is Nothing)) Then
   Text2.Text = g_doc1.Text
   Set g_doc1 = Nothing
End If

This is too complicated. It would be better if you could just ask for a reference to a UserDocument when you needed it. With the help of object guru, Matt Curland, you can add a simple collection class called ObjectCache to your project. It manages a list of loaded UserDocuments but won’t prevent any of them from unloading when they are released by the host. While the ObjectCache code may look like black magic, using it is trivial if you use the supplied project templates found on your VBITS CD-ROM. The template adds the following code to your UserDocument:

Private Sub UserDocument_Initialize()
    'When the UserDocument loads add it to the collection
    'of soft object references.  
    g_ObjectCache.Add Me, UserDocument.Name
End Sub

Private Sub UserDocument_Terminate()
    'The UserDocument is going away so remove it from the 
    ‘collection of soft object references
    g_ObjectCache.Remove UserDocument.Name
End Sub

Then in UserDocument2 when you want to reference UserDocument1, you use:

Dim doc1 As UserDocument1
Set doc1 = g_ObjectCache(“UserDocument1”)
Text2.Text = doc1.Text
‘Clear the reference returned by g_ObjectCache
Set doc1 = Nothing

g_ObjectCache is a collection object that uses the object names as keys for returning a strong reference to an object. See the ObjectCache and MultiDoc samples to see how this can really make a difference in your UserDocument programming experience.

Code Download

The Setup Wizard has an Internet Setup option that steps you through the necessary processes to download your UserDocuments onto client machines. The Setup Wizard uses the HTML CODEBASE tag to accomplish code download. This is not ideal. I’m working on an unsupported control that will improve code download and hyperlinking for UserDocuments. The control will first download any code onto the client’s machine that is required for the UserDocument, and then hyperlink to the UserDocument. The control also has a code download troubleshooting mechanism to help you debug problems. Check the AXDocHyperlink directory for more information. Component download will improve, and the Setup Wizard’s opening screen has a URL to a Web site that will contain the latest information and updates for the Setup Wizard.

The Origin of ActiveX Documents

To find out what the document in ActiveX Document means we need to look back at the roots of this technology. OLE Document Objects (the original name) first appeared in Office 95 along with the new Office Binder. All of the Office applications could be hosted inside the Office Binder so you could take a document-centric approach when working with multiple documents from multiple Office applications. The Office Binder consists of two panes, a narrow pane on the left with icons, one for each document, and a larger pane on the right where you view the contents of your document. Switching between a Word document and Microsoft Excel spreadsheet was as easy as clicking on the correct icon in the left pane. Best of all, your documents were saved into a single binder file (*.obd). The document in ActiveX Documents stems from the fact that all of the Office applications are used to create documents. However, the Office Binder in all the Office applications is not compiled into a single .exe. Instead, the Office Binder is an ActiveX Document Container and the Office applications (like Winword.exe) are all ActiveX Document Objects (really a form of OLE embedding). The Object is often dropped to read ActiveX Documents.

Miscellaneous Items

© 1997 Microsoft Corporation. All rights reserved.

The information contained in this document represents the current view of Microsoft Corporation on the issues discussed as of the date of publication. Because Microsoft must respond to changing market conditions, it should not be interpreted to be a commitment on the part of Microsoft, and Microsoft cannot guarantee the accuracy of any information presented after the date of publication.

This document is for informational purposes only. MICROSOFT MAKES NO WARRANTIES, EXPRESS OR IMPLIED, IN THIS SUMMARY.

Microsoft, Visual Basic, and Windows are registered trademarks and ActiveX and Win32 are trademarks of Microsoft Corporation.

Other product or company names mentioned herein may be the trademarks of their respective owners.