Jeff Webb, Mike McKelvy, Ronald Martinsen, Taylor Maxwell, Michael Regelski September 1995 Special Edition Using Visual Basic 4 - Chapter 15 1-56529-998-1 Computer Programming computer programming Visual Basic OLE database applications ODBC VB VBA API This book is an all-in-one reference that provides extensive coverage of every topic and technique for creating optimized and customized applications with Visual Basic.

Chapter 15

Designing OLE Container Applications


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:

Types of Applications You Can Create

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.

Creating a Simplified Shell

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.

Fig. 15.1a

The OLE Object Viewer (VIEW.VPJ) displays any object the user selects.

Fig. 15.1b

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

Option Explicit
Private Sub mnuView_Click()
' Show all filenames.
cmnDlg.FileName = "*.*"
' Display the Open common dialog.
cmnDlg.ShowOpen
' Make sure a file name was entered and that the file exists.
If (cmnDlg.FileName <> "") And Len(Dir(cmnDlg.FileName)) Then
' Display the file in the OLE object.
oleObject.CreateEmbed cmnDlg.FileName
End If
End Sub

The Exit menu item's Click event procedure merely gives the user a way to close the application:

Private Sub mnuitExit_Click()
End
End Sub

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.

' Resize the form to fit the object.
Private Sub oleObject_Resize(HeightNew As Single, _
WidthNew As Single)
Me.Height = HeightNew
Me.Width = WidthNew
End Sub

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

Private Sub mnuView_Click()
' New: Set up error handling.
On Error Resume Next
' Show all filenames.
cmnDlg.FileName = "*.*"
' Display the Open common dialog.
cmnDlg.ShowOpen
' Make sure a file name was entered and that the file exists.
Len(Dir(cmnDlg.FileName)) Then
' Display the file in the OLE object.
oleObject.CreateEmbed cmnDlg.FileName
' New: If the file was not an OLE object,
' load the file as a non-OLE object.
If Err Then DisplayNonOLEObject (cmnDlg.FileName)
' New: Hide other objects
imgObj.Visible = False
txtObj.Visible = False
oleObject.Visible = True
End If
End Sub

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

' Handles displaying other types of files.
Sub DisplayNonOLEObject(strFile As String)
' Check for errors.
On Error Resume Next
Dim strBuffer As String, iFile As Integer
' If the file is a graphic, display it in
' an Image control.
imgObj.Picture = LoadPicture(strFile)
' If the file was a valid picture, then
' display the image control and hide others.
If Err = 0 Then
' Hide other objects
imgObj.Visible = True
txtObj.Visible = False
oleObject.Visible = False
' Reset form's Height and Width to match
' Image control.
Me.Height = imgObj.Height
Me.Width = imgObj.Width
' If the file wasn't a valid picture, then
' load the data into a text box and hide other
' controls.
Else
iFile = FreeFile
Open strFile For Binary As iFile
strBuffer = Space(LOF(iFile))
Get iFile, 1, strBuffer
Close iFile
txtObj.Text = strBuffer
' Hide other objects
imgObj.Visible = False
txtObj.Visible = False
oleObject.Visible = False
' Reset form's Height and Width to match
' Text Box control.
Me.Height = txtObj.Height
Me.Width = txtObj.Width
End If
End Sub

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.

It's a Bird, It's a Plane, It's a Superclass!

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.

Fig. 15.2

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:

  1. Create a new project.
  2. Add an MDI parent form to the project (see fig. 15.3). The MDI parent form will display the OLE object's application menu and toolbar when the application runs. If the application you are superclassing doesn't display multiple windows, you can skip this step.

Fig. 15.3

Create an MDI parent form to contain OLE applications that display multiple windows.

  1. Add an MDI child form with an OLE control to contain an embedded object from the application you want to superclass (see fig. 15.4). You must use an embedded object (not linked) in order to get in-place editing.

Fig. 15.4

The MDI child form contains the OLE object from the application you want to modify—in this case, Shapeware's Visio drawing application.

  1. Add a File menu to the MDI child form. Be sure to set the Negotiate Position option to Left in the Menu Editor (see fig. 15.5).

Fig. 15.5

Setting Negotiate Position in the Menu Editor allows OLE objects to insert their application's menu and toolbar on the MDI parent form.

  1. Write code to perform the actions on the File menu on the MDI child form. Embedded objects do not provide File menu items, so you'll have to write code for these as well as for the new features you add to the File menu. Usually, you can delegate most of these tasks to the OLE application.

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:

  1. Initialize the OLE object at start-up. For an MDI application, this means displaying the child forms containing the embedded objects and activate those objects for in-place editing.
  2. Handle menu events from the File menu. Since embedded objects don't provide a File menu when edited in-place, you need to replicate the application's File menu both on your form and in your code. This is easy to do if the application supports OLE Automation, since you can delegate most tasks using methods provided by the application.

The following Load event procedure for an MDI parent form displays an MDI child form and activates the OLE object it contains for editing:

Private Sub MDIForm_Load()
' Show Visio drawing child form.
frmDrawing.Show
' Activate the object on the form for
' in-place editing.
frmDrawing.oleVisio.DoVerb
End Sub

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:

' Delegate Printing to the Visio application.
Private Sub mnuitPrint_Click()
' The Object property returns a Visio Page object,
' but Print applies to the Document object, so use
' Document.Print to print the object as shown here.
oleVisio.Object.Document.Print
End Sub

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:

' Event procedure for Find File command
' (new feature).
Private Sub mnuitFindFile_Click()
' Display the Find File dialog box.
frmFindVisoFiles.Show vbModal
End Sub

Adding OLE Automation to an Existing Application

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:

  1. Create a new project.
  2. Add an OLE control to Form1 that contains an object from the application that you want to make programmable. Figure 15.6 shows a WordArt object inserted on a form.

Fig. 15.6

The WordArt application is not programmable, although it does support other OLE features.

  1. Choose Tools, Options. Visual Basic displays the Options property pages (see fig. 15.7).

Fig. 15.7

The project name appears in the system registry after you compile and run the application from the resulting executable.

  1. In the Project Name text box, type the name of the OLE application that you are adding OLE Automation features to. Then select the StartMode OLE Server option button and click OK.
  2. Choose Insert, Class Module to add a class module to the project.
  3. In the Properties box for the class module, set both properties to True and name the module Application.
  4. Add code to the class module for the objects, properties, and methods to expose for the OLE object you inserted in step 2.
  5. Compile and run the project.

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)

' Inserts WordArt text in an OLE control.
Public Sub InsertText(strText As String)
' Using the WordArt object on frmWordArt
With frmWordArt.oleWordArt
' Embed a WordArt object in the passed-in OLE control.
.CreateEmbed "", "MSWordArt.2"
' Activate the object.
.Verb = -3
' Set the data format to text.
.Format = "CF_TEXT"
.DataText = strText
.UPDATE
.Close
' Pause to let the OLE application load.
' On Error Resume Next
'Do
' Err = 0
' DoEvents
' Activate the OLE application.
' AppActivate "Microsoft WordArt", False
' Loop While Err = 5
' On Error GoTo 0
' Set the text in the object.
' SendKeys strText, True
' Update the object
' SendKeys "%A", True
' Close the object
' SendKeys "{Esc}", True
' .UPDATE
' Close the object.
' .Close
End With
End Sub

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)

' APPLICAT.CLS
' Application class module for WordArt object.
'
' Pastes the WordArt object into the target control when
' the user performs and object Set.
Public Property Set Object(objTarget)
' Using the WordArt object on frmWordArt
With frmWordArt.oleWordArt
' Activate the object, hiding the running application.
.DoVerb -3
' Copy the object to the Clipboard.
.Copy
' Paste the object into the OLE target.
objTarget.Paste
' Close the object.
.Close
End With
End Property

Figure 15.8 shows using the new OLE Automation application from another Visual Basic application.

Fig. 15.8

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)

Private Sub cmdInsertText_Click()
' Create a new object variable.
Dim oleWordArt As Object
' Set the object variable to the VB
' WordArt.Application class created to
' make WordArt programmable.
Dim strText As String
strText = InputBox("Text to insert:")
Set oleWordArt = GetObject("", "wordart.application")
' Use the InsertText method from WordArt.Application.
oleWordArt.InsertText strText
' Insert the WordArt object into an OLE
' control on the current form.
oleWordArt.CopyObject
OLE1.Paste
End Sub

Integrating OLE Applications

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.

Who's in Control?

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.

Fig. 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:

Where Does the Code Live?

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.

VB4 versus VBA

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.

Fig. 15.10

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)

Sub VB4AccessTime()
Dim Sheet1 As Object, x As Object
Set Sheet1 = OLEOBJECT.object
time1 = Timer
For i = 1 To 1000
temp = Sheet1.UsedRange
Next i
MsgBox "Property access time: " & CSng(Timer - time1) / 1000
time1 = Timer
For i = 1 To 1000
Sheet1.Unprotect
Next i
MsgBox "Method access time: " & CSng(Timer - time1) / 1000
End Sub
Next, try listing 15.8's lines of code in VBA within a Microsoft Excel workbook.
Listing 15.8 Timing Cross-Process Access (ACCESS.VBP)
Sub VBAAccessTime()
Dim Sheet1 As Object, Temp As Object
Set Sheet1 = Sheets("Sheet1")
time1 = Timer
For i = 1 To 1000
temp = Sheet1.UsedRange
Next i
MsgBox "Property access time: " & CSng(Timer - time1) / 1000
time1 = Timer
For i = 1 To 1000
Sheet1.Unprotect
Next i
MsgBox "Method access time: " & CSng(Timer - time1) / 1000
End Sub

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.

Calling VBA Procedures from VB4 for Better Performance

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:

  1. Add the VBA procedure to the OLE object.
  2. Activate the OLE object.
  3. Call the VBA procedure.
  4. Deactivate the OLE object when you are finished calling VBA procedures.

Adding VBA Procedures to Embedded Objects

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:

  1. Open the OLE object in its application, as shown in figure 15.11. You must use the Open command rather than the Edit command in order to be able to edit VBA code in an embedded object.

Fig. 15.11

You must open the worksheet object in Excel to be able to add a module sheet for VBA code.

  1. From Excel's Insert menu, choose Macro, then choose the subitem Module to create a new module for your VBA code (see fig. 15.12).

Fig. 15.12

Inserting a new VBA module into an embedded object.

  1. Add your VBA procedure(s) to the new module. While in the object's application, you have access to VBA's Help and debugging features (see fig. 15.13).

Fig. 15.13

You can run, debug, and get Help on VBA statements as you write procedures for an OLE object.

  1. From Excel's File menu, choose Close to close the object and return to VB4.
  2. Save the VB4 form. Remember that changes to embedded objects are saved in the form at design time. Changes made at run time are lost unless you are using a linked object or have written special code to store changes to the embedded object before exiting.

Calling VBA Procedures from VB4

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:

ApplicationMethodExample
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
*Microsoft Word includes WordBasic, which also can be called from VB4.

To run a VBA procedure from VB4, follow these steps:

  1. Activate the OLE object using the DoVerb method. For example, this line of code activates an object using its default OLE verb—usually this is Edit or Open:

    oleObject.DoVerb

  2. Call the object's Run method. The actual name of the method varies from application to application. This line runs the procedure FillSheet in Excel:

    oleObject.Object.Application.Run "FillSheet"

  3. Use the OLE object's Close method to deactivate the object when you are done calling VBA procedures. This line of code deactivates the OLE object used previously in this procedure:

    oleObject.Close

Calling Excel VBA Procedures

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:

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:

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

Private Sub cmdSum_Click()
Dim objExcel As Object, lTotal As Long
' Activate the embedded worksheet before running the VBA
' procedure.
oleSheet.DoVerb
' Get a reference to the Excel Application object.
Set objExcel = oleSheet.Object.Application
' Run the VBA procedure SumColumn on the first column
lTotal = objExcel.Run("SumColumn", 1)
' Close the OLE object.
oleSheet.Close
' Display result
Msgbox lTotal
End Sub

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.

Function SumColumn(iCol As Integer) As Long
Dim i As Integer
For i = 1 To ActiveSheet.UsedRange.Rows.Count
lTotal = lTotal + Cells(i, iCol)
Next i
SumColumn = lTotal
End Function

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.

Calling WordBasic Macros

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:

wordbasic.ToolsMacro macroname, Run:=1

The following table describes the parts of Word's ToolsMacro method:

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

Private Sub cmdWord_Click()
Dim wrdBasic As Object
' Open the embedded object. Note, you must open
' Word objects -- WordBasic procedures are not
' available in Word's OLE Edit mode.
oleWordDocument.DoVerb 2
'Set wrdBasic = GetObject("", "Word.Basic")
' Get a reference to the WordBasic object from the
' embedded OLE object.
Set wrdBasic = oleWordDocument.Object.Application.WordBasic
' Call the ToolsMacro method on the WordBasic object.
wrdBasic.toolsmacro Name:="CreateLetter", run:=1
' Close the embedded object.
oleWordDocument.Close
End Sub

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

' NORMAL.DOT.This is a new letter
' CreateLetter WordBasic macro.
Sub MAIN
' Insert some text -- not very interesting,
' but you get the idea...
Insert "This is a new letter"
InsertPara
Insert "Dear Jane:"
InsertPara
Insert "I'm sorry I won't make it to your party."
Insert "I'm too busy programming VBA."
End Sub

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:

wordbasic.SetDocumentVar varname, value
wordbasic.[GetDocumentVar$] varname

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)

Private Sub cmdGetStyles_Click()
Dim wrdBasic As Object, strStyles As String
' Open the embedded object.
oleWordDocument.DoVerb 2
' Get a reference to the WordBasic object from the
' embedded OLE object.
Set wrdBasic = oleWordDocument.Object.Application.WordBasic
' Call the ToolsMacro method on the WordBasic object.
wrdBasic.toolsmacro Name:="ListStyles", run:=1
' Get the result from the StyleCount document variable.
strStyles = wrdBasic.[GetDocumentVar$]("gStyleName")
' Close the embedded object.
oleWordDocument.Close
' Display the result
MsgBox strStyles
End Sub

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

' ListStyles Macro in NORMAL.DOT
Sub MAIN
' Build a list of all the styles in a document.
For i = 1 To CountStyles()
sNames$ = sNames$ + Chr$(13) + StyleName$(i)
Next i
' Set the document variable -- notice that the variable name
' is in quotes.
SetDocumentVar "gStyleName", sNames$
End Sub

Trapping and Skinning OLE Errors

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.

Polling for Errors

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

Sub PollingDemo()
' (1) Start polling for errors.
On Error Resume Next
' (2) This line returns error 1004 if an outline can't be
' created.
oleSheet.Object.Selection.AutoOutline
' (3) If there was an error...
If Err Then
' (4) Alert user of the error.
MsgBox "Can't create outline on this selection."
Beep
' (5) Important! Reset error back to 0.
Err = 0
End If
'(6) Turn off error trapping.
On Error GoTo 0
End Sub

Listing 15.14 uses the following steps to poll for errors:

  1. The first step is to turn on polling. On Error Resume Next prevents errors from halting the program—instead, Visual Basic simply assigns the error value to Err and continues to the next line of code.
  2. The Excel Worksheet object's AutoOutline method does not work on all selections and it is impossible to test the selection to see if it will work before you call AutoOutline. Your only choice is to test after—possibly causing an error.
  3. If there was an error, Err is set to a non-zero value (in this case 1004). This tells you that the method failed, but unless you parse the string returned by Error, you can't tell what method failed. Parsing error strings is a bad idea, since they may change from version to version of Visual Basic. Your only real solution is to poll for errors after each method you think might fail.
  4. Alerting the user is a good idea. Here it is done through the status bar. Using the status bar rather than a message box is less intrusive and doesn't interrupt the user's work. Be sure to clear the status bar on the next user action so that the message doesn't stay up forever.
  5. Reset Err to 0. Otherwise, subsequent polling will reflect the current error value (1004) even if no error occurs.
  6. Turn off polling before exiting the procedure.

Using Error Handlers

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
Case 70 ' Permission denied.
Application.StatusBar = "Permission denied."
MsgBox "You can't open " & sFilename & _
". It requires a password or is in use by " & _
"another application."
iOpenFile = 0
On Error GoTo 0
Exit Function
Case 71 ' Disk not ready.
' Keep track of the number of tries.
iTries = iTries + 1
' Let the user try twice, but don't beat them
' over the head.
If iTries < 3 Then
Application.StatusBar = "Can't read from drive."
MsgBox "Make sure the disk is inserted and " & _
"the drive door is closed."
' Fail after second try -- maybe the user changed
' his/her mind.
Else
iOpenFile = 0
On Error GoTo 0
Exit Function
End If
Case Else
' Report the error, so you can fix it.
MsgBox "An unanticipated error occurred. " & _
Please report " & _
"the following information to AppBug: " _
& Chr$(10) & _
"Procedure: iOpenFile" & Chr$(10) & _
"Error: " & Err & Error()
iOpenFile = 0
On Error GoTo 0
Exit Function
End Select
' (6) You must tell Visual Basic to return after handling
' the error.
Resume
End Function
  1. The first step is to turn on error handling. On Error Goto label sets up an error handler—in this case iOpenFileErr.
  2. The Open statement might cause any number of errors. If an error occurs, the code jumps immediately to (3); otherwise it continues straight to (7).
  3. This big Select Case statement will handle all the known problems that might occur while opening a file.
  4. The most common problems can be solved by asking the user for the right file name. It is a good idea to let users correct problems if at all possible. This block of code uses Excel's Open File dialog box.
  5. In some cases, there is no easy solution, so the function may fail. It is a good idea to pass back 0 or False to indicate failure, rather than simply ending the program. This way, the caller can decide what to do (stop or continue on).
  6. After the error is handled, Resume returns execution to line (2) to open the file. Resume Next would return to line (7).
  7. Return values should be meaningful to the caller. In this case, if iOpenFile is not 0, then it's the file handle used to access and close the open file.
  8. Be sure to exit the procedure before the error handler. Otherwise, execution continues at (3) and so on—in this case, the Case Else would run if Exit was omitted.

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.

Error Handling as a Shortcut

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.

Sub ShortCuts()
On Error Resume Next
oleSheet.Object.UsedRange.Replace "wombat", "wallaby"
If Err Then MsgBox "Couldn't perform change."
Err = 0
On Error GoTo 0
End Sub

From Here...

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.