The Visual Programmer Puts ActiveX Document Objects Through Their Paces
Joshua Trupin is a software developer specializing in C/C++ and Visual Basic apps for Windows. He can be reached at firstname.lastname@example.org or email@example.com.
Click to open or copy the MFCX project files.
Click to open or copy the SCRIBX project files.
OLE has come a long way over the past few years, especially with regard to embedding technologies. At first, the best you could do was display an icon representing a server within a container and activate it sepaately by double-clicking on it. The next stage, a bit better, involved the static display of a server's contents within a rectangle. More recently, we've advanced to in-place editing of those embedded servers and to OLE control/ActiveX control interfaces.
ActiveX™ documents, also known as OLE Document Objects or doc objects, are the next logical step in this progression. If you're familiar with the MFC document/view design, you know that it specifies how programs create frames, views, and documents. Within the frame, the view window acts as a virtual display of the data contained in associated documents. Currently, an OLE 2 container/server interaction takes place in a rectangle granted to the server within the container's document. A doc object transaction takes place one level higher at the container application's frame level. A doc object server takes over the entire client area of the container application, can adjust the frame's menus, and appears as if it is the application. To put it another way, OLE documents describe a method for programs to write out data in a storage-independent way. ActiveX documents, on the other hand, are mechanisms that make an object container-independent and able to run in any frame.
If you have Microsoft® Office for Windows® 95, you can see how this works right now. Doc objects were originally designed for use with the Office Binder application (see Figure 1), which lets you collect disparate Office-based documents. As you click on the document icon, Office Binder adjusts the entire view area on the right, including toolbars and menus. To the user, it looks like working in a single application, since the frame itself never changes or goes away.
Figure 1 The Office Binder
The doc objects specification was first designed as a proprietary group of interfaces, used only in the context of the Microsoft Office Binder. (ISVs could sign up for the Office-compatible program, as outlined on the MSDN CD, to get specifications allowing them to Office-enable their own programs.) As the benefits of extending this concept became clear, the OLE doc objects specification was officially rolled out last December as part of Microsoft's ActiveX Technologies (formerly code-named Sweeper) client-side extensions. Microsoft Internet Explorer 3.0 will feature ActiveX document support, as will the Shell Explorer to ship with Nashville. ActiveX documents will soon permeate the entire Windows operating system line, so it's important that you become familiar with their workings and implementation. In the pages ahead I'll describe how to convert an existing MFC-based OLE server application into a doc object server, as well as how to provide doc object container support in your Visual Basic® 4.0 application. (My implementation of the MFC-based Document Object server is based on an early beta of MFC 4.2. MFC 4.2 should be available soon after this article appears in print.) Although Visual Basic 4.0 can't create ActiveX Document objects, Visual Basic 5.0, expected out at the end of this year, will provide this new feature (among other stuff too cool to mention here).
ActiveX Document OLE Interfaces
If you want your application to be an ActiveX document, able to work within an ActiveX doc object container, you must do four things:
- Implement IPersistStorage to use OLE compound files as a storage mechanism.
- Support basic OLE document-embedding features, such as the Create From File menu pick. This necessitates the interfaces IPersistFile, IOleObject, and IDataObject.
- Support in-place activation of OLE Documents through IOleInPlaceObject and IOleInPlaceActiveObject. This implies knowledge of a container's IOleInPlaceSite and IOleInPlaceFrame interfaces.
- Support the ActiveX document extensions through four new server-side interfaces: IOleDocument, IOleDocumentView, IOleCommandTarget, and IPrint.
That slightly glazed-over look in your eyes can only mean one thing-you thought you were going to be able to avoid OLE for just a bit longer. A chart or two might make these specifications slightly less daunting. Figure 2 shows a slightly more readable form of the information in the list above, including the newly defined ActiveX document interfaces. If you look at this chart and feel like you're drowning in interfaces, don't worry. In the land of MFC, you don't have to implement everything by hand.
Figure 2 ActiveX Document Interfaces
As OLE has evolved, the document/view system used in a standard MFC application has been seamlessly integrated with coordinating OLE interfaces. This is all done quietly within the base class definitions, so you never even know about it unless you browse through the header files. If you compare the basic output of MFC 1.0's AppWizard and today's version, the program might look about the same. However, it will support a broad array of OLE embedding functions, all of which you get for free.
Not only do you get stuff without asking for it, you may also discover that your application already has a dreaded interface implemented, buried somewhere in the depths of the MFC base classes. This makes it clear why you should install MFC source code along with the Microsoft Developer's Studio, even though it takes up some more disk space.
Take a closer look at Figure 2. On the left, you'll see what you have to implement to become an ActiveX document container. We don't need to know about any of that right now, since we're concentrating on writing embeddable applications, not frames. You'll get all the stuff on the left when you open up Internet Explorer 3.0 or Microsoft Office Binder (or the Visual Basic sample later in this article). It's the stuff on the right, the server specification, that interests us.
Let's concentrate on everyone's favorite sample, the MFC Scribble application. When you implement a standard document/view MFC application like Scribble, your document class gets an appropriate name like CScribbleDoc. This class is derived from COleServerDoc, which is several layers away from the simple base class CDocument. As it turns out, these layers contain a lot of OLE functionality. Most of the interfaces you need to implement an ActiveX document server are already encapsulated within your CScribbleDoc (see Figure 3). In fact, without touching a line of code, six of the ten server-side interfaces already exist-you only have to implement the four new ones (IOleDocument, IOleDocumentView, IPrint, and IOleCommandTarget).
Figure 3 Where the Interfaces Are
Obviously, each of these four remaining interfaces is important for an ActiveX document server's health. Before we look at how you can add these interfaces to an existing project, we need to investigate just what they do. These four interfaces are all defined in DOCOBJ.H; a simplified version (based on the DOCOBJ.IDL interface definition language file) is shown in Figure 4.
By implementing the IOleDocument interface, an object indicates that it can act as an ActiveX document. An ActiveX document container uses a server's IOleDocument to create new server views, enumerate these views, and retrieve MiscStatus bits relevant to the ActiveX document. (What we call a view is really an IOleDocumentView, an interface I'll discuss shortly.) IOleDocument implementations therefore need three functions: CreateView, EnumViews, and GetDocMiscStatus. CreateView creates and allows (optional) initialization of a new view. GetDocMiscStatus returns the value of the object's MiscStatus bits. These are also saved in the doc objects registry key of this object. EnumViews returns an enumeration of the document's views, or a pointer to its single view. IEnumOleDocumentViews, the returned enumeration, is a standard enumerator interface typed for IOleDocumentView. (An enumerator interface is defined with the operators Next, Skip, Reset, and Clone, and allows its user to walk through a list of document view objects.)
The IOleDocumentView interface refers to a single instance of an ActiveX document view, a concept closely related to an MFC view. The interface provides everything needed for a container to perform site management-sizing, activating, and resetting an ActiveX document's bounding area. To do this, IOleDocumentView implements in-place site functions, as well as others like GetRect, Show, UIActivate, and Clone (see Figure 5). When implemented within an MFC-based document/view application, information sent to the IOleDocumentView interface by the host will be reflected back to the MFC view class, allowing it to adjust itself accurately. For example, IOleDocumentView is notified when you resize the container.
IOleCommandTarget is an optional interface that lets ActiveX document container and servers send each other commands, without resorting to tricks such as assigning fixed menu IDs. Instead, this mechanism lets a caller query it to find out which commands it supports (from a standard enumerated list) as well as letting an object execute all supported commands. Two functions are provided: QueryStatus returns an object's support for a particular command; Exec asks an object to execute a supported command, indicated by ID.
A caller passes in a list of OLECMD structures, each of which contains a command ID and a place to put informational flags on return. The implementor fills in each structure with one or more OR'd flags from the list shown in Figure 6. An OLECMDTEXT list works the same way, receiving a localized name and status string for each command requested. This allows a hosting program to change its status bar when a command is invoked.
Using the Exec function, you can call a specific command by ID (for instance, OLECMDID_OPEN is equivalent to File Open), and provide incoming and outgoing arguments (although these are usually NULL). There are several options defined within the OLECMDEXECOPT enumeration that indicate how the object should execute this command (see Figure 7).
A number of common commands have already been defined in the IOleCommandTarget interface for use with the Exec function. These mostly deal with the File and Edit menus
Like IOleCommandTarget, IPrint is an optional addition that lets an object support programmatic printing. The three functions provided by IPrint tell the server object to print (Print), retrieve print-related information (GetPageInfo), and set the initial page number of a print job (SetInitialPageNum). When the IPrint::Print function is called, it takes as input any of a group of flags indicating what defaults are set for the print job (see Figure 9).
Adding ActiveX Document OLE Interfaces To An MFC Project
Microsoft beat me to the punch by including a sample called BINDSCRB in Visual C++® 4.1. This sample includes the implementation of the ActiveX document extensions, and shows how to write a program that uses them. Visual C++ 4.2, slated for release shortly after this article goes to press, will "naturalize" this process, migrating these interface definitions and their class wrappers to standard MFC, making it far easier to design a full-blown ActiveX document server.
The BINDSCRB sample includes nine CPP implementation files: BINDDCMT.CPP, BINDDOC.CPP, BINDIPFW.CPP, BINDITEM.CPP, BINDTARG.CPP, BINDVIEW.CPP, MFCBIND.CPP, OLEOBJCT.CPP, and PRINT.CPP. Additionally, four header files are used the implement ActiveX documents: BINDDOC.H, BINDIPFW.H, BINDITEM.H, and MFCBIND.H. (These names may change before the final release of Visual C++ 4.2.) To make it easier to use these files, they are all wrapped up in AFXBIND.H, which can be inserted into an STDAFX.H file.
These files introduce three new classes to MFC: CDocObjectIPFrameWnd (COleIPFrameWnd with ActiveX document extensions), CDocObjectServerDoc (based on COleServerDoc), and CDocObjectServerItem (from COleServerItem). In each case, these classes extend the main classes used to build MFC documents.
Look at the files in the Scribble application (as defined as a Visual C++ 4.0 sample). Three major classes-CInPlaceFrame, CScribbleDoc, and CScribbleItem-are defined as follows:
class CInPlaceFrame: public COleIPFrameWnd
class CScribbleDoc: public COleServerDoc
class CScribbleItem: public COleServerItem
Since the new CDocObjectXxx classes are derived from the Scribble base classes directly, they can be popped in the Scribble class definitions as replacements. Turn Scribble into an ActiveX document server by changing the lines above to look like these:
class CInPlaceFrame : public CDocObjectIPFrameWnd
//(see file IPFRAME.H)
class CScribbleDoc: public CDocObjectServerDoc
//(see file SCRIBDOC.H)
class CScribbleItem: public CDocObjectServerItem
//(see file SCRIBITM.H)
This will soon be an option within the Visual C++ AppWizard, but it's a by-hand process right now. Also, your program must be tagged in the registry as supporting the ActiveX documents specification. MFC already takes care of registration settings every time a program executes, so this isn't a huge burden. In the CScribbleApp::InitInstance routine, adding one call to a predefined function takes care of this for you.
The MFC base class overrides handle the details of the ActiveX implementation. The source code for updated portions of Scribble is shown in Figure 10.
Getting There In Visual Basic
As I've mentioned before, ActiveX documents are a feature of Microsoft Internet Explorer 3.0, and will be supported in the Shell Explorer in future versions of Microsoft Windows. You can't directly automate Internet Explorer from Visual Basic, but you can get to the important part: the browser window. The ActiveX SDK (currently available at http://www.microsoft.com/intdev/sdk/) contains a file called SHDOCVW.DLL, which is really an OLE control. Installing the ActiveX SDK automatically registers this file. It shows up in the Visual Basic 4.0 (32-bit version) custom control list as "Microsoft Internet and Shell Controls."
This DLL provides two controls visible in the Visual Basic toolbar: ShellExplorer and ShItemOC (shell item). The ShellExplorer control interests us most because it's an OLE control wrapper around the browser window included in Internet Explorer 3.0. Unlike the HTML control in the Internet Control Pack (which interprets HTML input and shows it in a screen), this is an actual COM wrapper around Internet Explorer 3.0. It exposes all the functionality of that product, from Visual Basic Script to ActiveX documents. If the hosting program has a menu, this control also performs proper negotiation. When you're looking at a Web site, you'll get the Internet Explorer 3.0 menu, allowing you to view a site's source-or refresh it-without any work on your part.
I'm mentioning this control because it gives you all you need to write a Visual Basic program that's also an ActiveX document container. This is logical because Internet Explorer 3.0 is an ActiveX document container and you're getting all its functionality. The simplest way to get this working is to open a new project in Visual Basic 4.0 (this is 32-bit only) and add the "Microsoft Internet and Shell controls" choice from the Tools/Custom Controls menu (or by right-clicking the toolbox). Two new control icons appear. Add a ShellExplorer to your project's form (make it as big as possible), then add three buttons and a text box to the form. Finally, add a menu with anything on it-a simple File/Exit will suffice. Now that you have a form set up, you only have to enter three lines of code to create a simple, yet highly functional, Web browser and ActiveX document shell.
Private Sub Command1_Click()
ShellExplorer1.Location = Text1.Text
Private Sub Command2_Click()
Private Sub Command3_Click()
The first button sets the browser window's location to the string in the text box. The second and third are the Back/Forward controls you'd have in a full-blown browser. When the program first runs, the ShellExplorer displays the contents of your system, similar to the My Computer icon in Windows 95. When you enter a new Web address in the text box and click the first button, the site displays automatically, including any necessary dial-ups (see Figure 11). Clicking on an HTML hyperlink loads the new page without any intervention on your part.
Figure 11 Viewing a Web Site
You can also get to a file by clicking on it in the My Computer screen. However, in my beta release of Internet Explorer 3.0, this method loads the file into the associated program in a separate window, even if the program is an ActiveX document server. If you type a filename into the textbox and click the first button, the file ShellExplorer control Loads the file as a working ActiveX document. Try it with a Microsoft Word or Microsoft Excel file, or even better, check out a file saved with the modified Scribble application-such as MSJSCRIB.OSC (see Figure 12).
Figure 12 Viewing a Scribble document
ActiveX documents began life as Office Compatible objects, and this holds true today. To be hosted in the Microsoft Office Binder, an application must support the ActiveX document interfaces and OLE structured storage. But what started as a proprietary interface, unavailable to most users, has become an emerging standard in advanced OLE technology.
ActiveX document interfaces will be the basis of future Windows shells, starting with the Shell Explorer in Nashville. You can start to develop and test both documents and viewers with tools available today-Microsoft Visual C++ 4.0, Visual Basic 4.0, and the free ActiveX SDK. By making a few very simple changes in existing MFC-based projects, you can dramatically extend your OLE functionality, and by writing a short program in Visual Basic, you can get a jump on the next release of the Windows shell.
From the June 1996 issue of Microsoft Systems Journal.