This chapter describes the types of applications you can create using Visual Basic's OLE container features. It tells you how to go about creating each of these types of applications and provides guidelines on dealing with general programming problems such as control flow and error handling in OLE container applications.
In this chapter, you learn how to do the following:
You can create Visual Basic OLE container applications that accomplish any of the following tasks:
The following sections discuss each of these types of applications in turn.
Most OLE applications are highly customizable, but even so they never match the level of control you have in Visual Basic. This makes Visual Basic a very good tool for creating simplified interfaces for more complicated underlying applications.
Figure 15.1 shows how easy it is to create limited access to a variety of OLE objects. The Object Viewer is simply an OLE control on a single form with a Common Dialog control to allow the user to select a file to view.
The OLE Object Viewer (VIEW.VPJ) displays any object the user selects.
The OLE Object Viewer (VIEW.VPJ) displays any object the user selects.
The code for the Object Viewer is extremely short. The View menu item's Click event procedure, shown in listing 15.1, displays the File Open common dialog box and then loads the selected file into the OLE control using the CreateEmbed method.
Listing 15.1 Use CreateEmbed to Load OLE Objects from Files Quickly
The Exit menu item's Click event procedure merely gives the user a way to close the application:
And finally, the OLE control's Resize event procedure sizes the form to match the object. This assumes that the OLE control's SizeMode property is set to vbOLESizeAutoSize—otherwise, the object is clipped to fit in the control.
The Object Viewer is about as simple as an OLE Container application can get, yet it is still extremely useful for viewing files without worrying about what application they came from. Of course, the Viewer doesn't handle non-OLE files very elegantly—to do this, you need to add some error handling, such as the modifications to the View_Click event procedure shown in listing 15.2.
Listing 15.2 Modify mnuView_Click to Handle Non-OLE Objects
It is safer to try CreateEmbed and catch the error than to try to determine whether the file is a valid OLE file by looking at the extension in the file name (.XLS, .DOC, and so on). Any file may have these extensions—not just OLE files. The DisplayNonOLEObject procedure, shown in listing 15.3, handles the other file types. Graphic files are displayed in an image control and other file types (.TXT, .INI, and so on) are displayed in a text box control.
Listing 15.3 DisplayNonOLEObjects Uses Text Box and Image Controls to Display Other Types of Objects
Listings 15.2 and 15.3 use a style of error checking called polling. The On Error Resume Next statement tells Visual Basic to temporarily ignore trappable run-time errors and continue executing code—you check the value of Err after each line that you expect might return an error (If Err...Then...). This style of error handling is very useful when working with OLE objects. For more information on error handling with OLE objects, see the section "Trapping and Skinning OLE Errors" later in this chapter.
Relative to the processing and memory requirements of a full-blown drawing, spreadsheet, or word processing application, the overhead you incur by adding Visual Basic is minimal. You may choose to operate an OLE application—with all of its user interface intact—from within an OLE control on a form. This provides a way to add menu items and features to an application that is not otherwise programmable.
Figure 15.2 shows a Visual Basic application that adds two menu items to the Visio file menu. Visio automatically adds its own menu bar and toolbar to the Visual Basic MDI form. You only have to program the items on the File menu. Creating a new object that modifies a contained object is called superclassing the object. In this case, you are using Visual Basic to superclass an entire application.
You use Visual Basic to "superclass" OLE applications such as Visio to add menu items and other features.
To superclass an OLE application using Visual Basic, the application must support in-place editing.
To superclass an OLE application using Visual Basic, follow these steps:
Create an MDI parent form to contain OLE applications that display multiple windows.
The MDI child form contains the OLE object from the application you want to modify—in this case, Shapeware's Visio drawing application.
Setting Negotiate Position in the Menu Editor allows OLE objects to insert their application's menu and toolbar on the MDI parent form.
The form's NegotiateMenus property also determines whether OLE objects can display their menus on the form's menu bar. This property is True by default.
When superclassing an OLE application, you need to write code for two important tasks:
The following Load event procedure for an MDI parent form displays an MDI child form and activates the OLE object it contains for editing:
The MDI child form frmDrawing contains the File menu for Visio, plus two new menu items (Find File and Add Routing Slip). Since Visio supports OLE Automation, you can use Visio methods to reproduce the standard File menu actions. For example, the Click event for the Print menu item simply calls the Visio Print method on the OLE object, as shown here:
Using the object's application methods in this way is called delegating—your application delegates the task to the OLE application. Of course you can't delegate tasks that Visio doesn't ordinarily provide, such as Find File. To perform this task, you need your own code, as shown here:
You can use Visual Basic's OLE Automation features to add objects, properties, and methods to an existing OLE application. Part III of this book, "Creating OLE Objects," covers Visual Basic's OLE Automation features in great detail, but this task is worth mentioning here since it deals with developing OLE container applications. To make an existing OLE application programmable as a Visual Basic OLE server, follow these steps:
The WordArt application is not programmable, although it does support other OLE features.
The project name appears in the system registry after you compile and run the application from the resulting executable.
Listing 15.4 demonstrates how to create a method in a class module. The InsertText method inserts a string into the WordArt object on the form frmWordArt.
Listing 15.4 Adding a Method to Insert Text in the WordArt OLE Application (WORDART.VBP)
Listing 15.5 demonstrates how to create an object property in a class module. The Object method copies the object contained in the oleWordArt object to the object on the left side of a Visual Basic Set statement (for example, Set objTarget = objWordArt.Object).
Listing 15.5 Adding an Object Property to the WordArt OLE Application (WORDART.VBP)
Figure 15.8 shows using the new OLE Automation application from another Visual Basic application.
The Test application calls the new WordArt application to insert text in a WordArt object and display it.
Listing 15.6 demonstrates how to call the OLE Automation application you just created from another Visual Basic application. The CreateObject function starts the application and returns a reference to an instance of the WordArt.Application class shown in listing 15.5. Then the method InsertText draws a text string using WordArt, and the Set statement displays the object in an OLE object on the current form.
Listing 15.6 Using the New WordArt Methods and Properties from Another Application (TESTWORDART.VBP)
Of course, the biggest single advantage of OLE is that it lets you integrate applications so that they can work together. You can create sophisticated software applications by borrowing features from a suite of OLE applications, such as Microsoft Office or Lotus SmartSuite.
From the standpoint of creating OLE container applications using suites of applications, you need to consider a few major points:
Part IV of this book, "Integrating with Office Applications," is dedicated to doing this type of system integration using the Microsoft Office Development Kit.
OLE containers pass control back and forth between the applications that provide the OLE objects and the application that contains them. Since OLE objects may themselves contain embedded objects, it can be difficult to determine who's currently in control, as shown in figure 15.9.
Control passes from application to application as a user clicks around in a complicated OLE container application; the numbers indicate the flow of control.
Knowing who is in control is critical to tracking down errors and anticipating the behavior of an application. For instance, if the user opens another document in an application while editing an embedded object, does the container application know not to close the object's application—and thereby avoid losing the user's edits? Alternately, how does your OLE container application recover if an OLE object's application crashes?
Here are some guidelines that help contain these problems when programming with OLE objects:
OLE doesn't just distribute control between applications, it also lets you distribute code. For example, you can use Visual Basic 4.0 (VB4) to call Visual Basic for Applications (VBA) procedures that reside in an Excel worksheet. The VBA procedures can function just like VB4 procedures, receiving arguments and returning values.
When designing your container application, you must decide where code should live. Keeping all your code in the VB4 project makes it easier to maintain your code, since all files are stored in the same format in a single location. Using VBA procedures has performance advantages for some tasks, however.
The Visual Basic Programming System, version 4.0, (VB4) includes the same "engine" that drives Visual Basic for Applications (VBA). VBA is the programming language shipped with Microsoft Excel, Project, and (eventually) other Microsoft Office applications. Programming in VBA is very much like programming in VB4—there is a code window and a similar debugger, and all the "core" language is the same. Many of the OLE features that are available in VB4 are also available to VBA from within the application.
So why program in VB4? VB4 has these advantages over VBA:
The disadvantage of VB4 is that it takes longer to access an OLE object's properties and methods from VB4 than it does to access that object's properties and methods from within its application using VBA. The reason for this is that VB4 must cross process boundaries to get at an OLE object's properties and methods, whereas VBA has in-process access to all of the application's objects. Figure 15.10 shows the difference between in-process and cross-process access.
Cross-process versus in-process access to objects.
Cross-process access to an object's properties is 2 to 20 times slower than in-process access. Access to methods is 15 to 30 times slower cross-process than in-process. To see the performance difference between cross-process and in-process access, try listing 15.7's lines of code in VB4 using an embedded worksheet from Microsoft Excel named oleObject.
Listing 15.7 Timing In-Process Access (ACCESS.VBP)
The access time between VB4 and VBA is significantly different for the UsedRange property and Unprotect method: UsedRange takes twice the amount of time cross-process; Unprotect takes 19 times more time cross-process. These differences vary greatly depending on the property or method used. Operations that cause a change in the display of the object can mask performance differences since the screen update takes a fixed amount of time, regardless of the source of the request.
If an OLE application includes VBA, you can write performance-critical procedures in VBA, then call them from VB4. Access to properties and methods stays in-process, so these actions take place much more quickly.
To use VBA procedures from VB4, follow these steps:
You can add VBA procedures to linked or embedded objects on a VB4 form. With linked objects, the procedures are stored in the separate, linked source file. With embedded objects, the procedures are stored in the VB4 form as part of the OLE object.
To add a VBA procedure to an OLE object, follow these steps:
You must open the worksheet object in Excel to be able to add a module sheet for VBA code.
Inserting a new VBA module into an embedded object.
You can run, debug, and get Help on VBA statements as you write procedures for an OLE object.
The OLE object must be active before you can call a VBA procedure in the object. Activating the object loads the object in the object's application and makes the VBA procedures available to run. Each OLE application provides different methods for running VBA procedures, as shown in the following table:
Application | Method | Example |
---|---|---|
Microsoft Excel | Run | oleObject.Object.Application.Run "FillSheet" |
Microsoft Project | MacroRun | oleObject.Object.Application .MacroRun "AssignTask" |
Microsoft Word* | ToolsMacro | oleObject.Object.Application. WordBasic.ToolsMacro _ "CreateLetter", 1 |
To run a VBA procedure from VB4, follow these steps:
oleObject.DoVerb
oleObject.Object.Application.Run "FillSheet"
oleObject.Close
Use the Excel Application object's Run method to call VBA procedures from VB4. You can get a reference to the Excel Application object two ways:
Set objExcel = CreateObject("excel.application")
Set objExcel = oleWorsheet.Object.Application
Once you've obtained a reference to the Excel Application object, use the Run method to run the VBA procedure. Run has the following form for VBA Sub procedures:
appobject.Run [workbook!]procedurename[, arguments...]
The Run method has the following form for VBA Function procedures:
appobject.Run([workbook!]procedurename[, arguments...])
The following table describes the parts of Excel's Run method:
Item | Description |
---|---|
appobject | A reference to the Excel Application object. In VBA this item is optional, but it's required when calling from VB4. |
Run [workbook!] |
The Run method. The file name of the open workbook that contains the procedure. If omitted, Excel uses the current workbook. Excel does not automatically open workbook if it is not already loaded. |
procedurename [, arguments...] |
The name of the procedure to run. A list of the arguments to pass to the VBA procedure. |
If the VBA procedure is a function, Run returns the result of the function. For example, the code in listing 15.9 runs the SumColumn function in Excel and returns the total of all the cells in a column on a worksheet.
Listing 15.9 Using VBA Procedures Stored in an Embedded Object (OLECHART.VBP)
The SumColumn VBA function is shown in the following code. This function can be inserted in a module in the workbook of the oleSheet object, or it can be in any workbook currently loaded in Excel.
You can't pass objects to VBA procedures, since VB4 evaluates all items in the argument list before passing them on the to procedure. In other words, VB4 attempts to pass the Value property of an object, rather than the object itself, when you use the Run method. Similarly, you can't receive objects as return values from the Run method—Excel simply returns an error. Even if you could receive an object back from Run, you couldn't use it to set the OLE control's Object property, since it is read-only.
Use the WordBasic object's ToolsMacro method to call WordBasic macros from VB4. You can get a reference to the Word WordBasic object two ways:
Once you've obtained a reference to the WordBasic object, use the ToolsMacro method to run the WordBasic macro. ToolsMacro has the following form for running WordBasic macros:
The following table describes the parts of Word's ToolsMacro method:
Item | Description |
---|---|
wordbasic | A reference to the WordBasic object. |
ToolsMacro | The ToolsMacro method. |
macroname | The name of the macro to run. WordBasic macros are stored in Word template files (.DOT). Only macros in currently active templates are available from VB4. |
Run:=1 | Tells Word to run the macro. ToolsMacro has other arguments for displaying and editing macros. See the WordBasic documentation for more information on these arguments. |
WordBasic macros are available only if the Word application is open for editing. You can't use WordBasic macros on embedded objects that are activated for in-place editing. The VB4 code in listing 15.10 opens an embedded Word document in Word and runs the CreateLetter procedure.
Listing 15.10 Running a WordBasic Macro from VB4 (ACCESS.VBP)
The CreateLetter WordBasic macro, shown in listing 15.11, must exist in the global template (NORMAL.DOT) or the document's current template file in order to be run from VB4.
Listing 15.11 The WordBasic Macro to Run
WordBasic macros can't receive arguments or return values. However, you can use Word's document variables to achieve the same affect. In Word, a document variable is a static, global variable that all macros can read from or write to within the document. Use the WordBasic SetDocumentVar and GetDocumentVar$ methods to set and return values from document variables. The two methods have this form when used from VB4:
Be sure to enclose GetDocumentVar$ in square brackets in your VB4 code, since it includes a dollar sign ($), which has special meaning to Visual Basic. The VB4 code in listing 15.12 uses a document variable to return the number of styles contained in the style sheet for a document.
Listing 15.12 Getting Values Back from WordBasic Macros (WORD.VBP)
The ListStyles WordBasic macro, shown in listing 15.13, must exist in the global template (NORMAL.DOT) or the document's current template file in order to be run from VB4.
Listing 15.13 Sending Values to VB4 from a WordBasic Macro
When programming with OLE objects, you may receive trappable errors from three sources:
There are two programming strategies for trapping these errors:
Polling is essential when you're programming with OLE objects. Errors from OLE object applications tend to be vague—Excel defines seven errors to cover about 1400 methods and properties—you usually need to know exactly what line of code failed in order to handle the situation effectively.
This method is called polling because you check for errors after each statement that might cause an error. The advantage of this is that you know exactly what line of code caused the error. Listing 15.14 shows how to poll for errors.
Listing 15.14 When Polling for Errors, Don't Abandon the Punt in Favor of the Pole
Listing 15.14 uses the following steps to poll for errors:
Error handlers are blocks of code that handle a general set of errors. One of the nice things about handlers is that they move all the error handling code to the end of the procedure—out of the main logic of the procedure.
Unlike polling, execution doesn't continue in a straight line from the top to the bottom of a procedure. Instead, execution jumps to the error handler when an error occurs. Listing 15.15 shows how you might use error trapping by setting up an error handler.
Listing 15.15 Using Error Handlers (ERROR.VBJ)
' Opens a file for Input. Returns file number if successful,
' 0 if failure.
Function iOpenFile(sFilename As String) As Integer
Dim iFilenumber As Integer, iTries As Integer
' (1) Turn on error handling.
On Error GoTo iOpenFileErr
' Get a free file number.
iFilenumber = FreeFile()
' (2) Open file. We don't know if the file exists yet,
' so might cause an error.
Open sFilename For Input As iFilenumber
' (7) Return file number so user can manipulate file.
iOpenFile = iFilenumber
' Important! Turn off error handling.
On Error GoTo 0
' Clear status bar.
Application.StatusBar = ""
' (8) Important! Exit procedure before the error handler.
Exit Function
' (3) Label identifies error handler. This label must be unique
' to the workbook.
iOpenFileErr:
' Classic way to do this: Select Case on the error code,
' with a Case statement for each possibility.
Select Case Err
Case 52, 53, 75, 76 ' Bad file name or number,
' file not found, or path error.
' (4) Display a status message
' indicating the error.
Application.StatusBar = "File not found."
' Prompt the user for the file to open.
sFilename = Application.GetOpenFilename _
(, , "Choose file to open")
' If the user chose Cancel...
If sFilename = "False" Then
' Return 0 to indicate that function didn't
' open a file.
iOpenFile = 0
' Turn off error handling and exit.
On Error GoTo 0
Exit Function
End If
Case 55 ' File already open by VB for an incompatible
' read/write access.
' This shouldn't happen, but if it does, return 0
' and exit.
iOpenFile = 0
On Error GoTo 0
Exit Function
Case 67 ' (5) Too many files are open
Application.StatusBar = "Too many files or " & _
applications open."
MsgBox "Close one or more files or " & _
"applications and try again."
iOpenFile = 0
On Error GoTo 0
Exit Function
Name error handlers after the procedure they reside in using this form: procedurenameErr. This way, they will always be unique and you'll avoid naming conflicts.
Sometimes it's easier to test for an error rather than making sure an action is valid before performing that action. This is a very common practice in BASIC programming—nothing happens faster than an error, so it's also a way to speed up some operations.
The following code replaces "wombat" with "wallaby" on the active sheet. Several errors might occur: the embedded object might not be a worksheet and therefore not have a UsedRange property, it might be protected, or it might be open for read-only access. Rather than testing each case, the code simply tests for an error after the Replace.
For more information on OLE objects and applications, see the following chapters:
© 1996, QUE Corporation, an imprint of Macmillan Publishing USA, a Simon and Schuster Company.