Jeff Webb, Mike McKelvy, Ronald Martinsen, Taylor Maxwell, Michael Regelski September 1995 Special Edition Using Visual Basic 4 - Chapter 25 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 25

Understanding VBA and Other Microsoft Basic Languages


On May 20, 1991, Microsoft announced Visual Basic at Windows World '91 in Atlanta, and the programming world hasn’t been the same since. Windows programmers finally had a very powerful programming language that was simple to use and very intuitive. Visual Basic for Applications takes the success of Visual Basic one step further, and in this chapter you learn how VBA helps make your job as a programmer much easier.

In this chapter, you learn more about the following:

Introducing Visual Basic for Applications (VBA)

Visual Basic for Applications (VBA) is Microsoft's attempt to create a common macro language for its products. VBA is based on the highly successful Visual Basic (VB) product. This second generation version of Visual Basic is as easy to use as VB and includes many new enhancements to the language. Some of its major enhancements include the following:

Most of the improvements help to make VBA a more robust language for its most important feature, OLE Automation.

For a more exhaustive discussion on VBA, read Using Visual Basic for Applications, Excel Edition, by Jeff Webb. This book contains useful information for using VBA with the most powerful OLE 2.0 server, Microsoft Excel.

Understanding the Differences between VB and VBA

Although VB and VBA are very similar, they differ in some important ways. Perhaps the most important difference is the definition of the two languages. Table 25.1 provides these definitions.

Table 25.1 Definitions of VB and VBA

Language Definition
Visual Basic As of Visual Basic 4.0, this version is actually VBA 2.0. It differs from VBA in that it enables you to create a stand-alone executable that you can distribute to users who do not own a Microsoft application.
Visual Basic for Applications Introduced in the fall of 1993 as a replacement for Microsoft Excel's XLM macro language, VBA 1.0 is currently included in Excel 5.0 and Project 4.0, and its applications require the product in which they were created.

All this stuff is rather confusing, but there's a simple way to differentiate between the two: VB is a separate programming product that enables you to make executables, and VBA is a new macro language for Microsoft's applications. As of VB 4.0, both products can share elements (type libraries) through the Object Browser. Currently only Excel 5.0 and Project 4.0 have VBA, but eventually all Microsoft languages will adopt VBA as a common macro language.

Using Visual Basic for Applications

You are probably wondering whether you can simply look at a code sample and automatically distinguish VB code from VBA code. You cannot; instead, you must look at the editor in which the code was written. For example, the following procedure could appear in either VB or VBA:

Sub Greetings(YourName As String)
Dim Reply As Integer
Reply = MsgBox("Hello " & YourName & _
", are you ready to compute?", vbQuestion + vbYesNo)
If Reply = vbYes Then
MsgBox "Well then, let's get busy!", vbInformation
Else
MsgBox "I'm sorry to hear that, but you must.", _
vbInformation
End If
End Sub

By looking at this code in its editor, you can tell which language was used. Figure 25.1 shows an example of VBA in Microsoft Excel, and figure 25.2 shows an example of Visual Basic 4.0. Viewing the editor makes a difference because all VBA editors conform to the same basic rules.

Fig. 25.1

VBA code in Microsoft Excel 5.0.

Fig. 25.2

The same code in Visual Basic 4.0.

Type Libraries and the Object Browser

How can one language meet all the needs of three separate products? That’s where type libraries enter the picture. A type (or object) library is a file that each variation of Visual Basic provides to add product-specific features (for example, accessing a range of cells in Excel). In fact, all products with VBA share a common VBA type library. What's more, any variation of VBA can use type libraries from other applications to perform OLE Automation.

Type libraries are managed by a program, the Object Browser, that is built in to VBA. Object Browser (see fig. 25.3) provides the user with a list of the available libraries and their elements.

Fig. 25.3

The Object Browser in Visual Basic 4.0 provides easy access to type libraries.

Visual Basic 4.0's version of the Object Browser (VBA 2.0) is slightly enhanced from VBA 1.0's, but their purposes are identical.

Other Basic Languages from Microsoft

New programmers often complain that they are confused by all the "Basics" found in Microsoft products. This section clears up the confusion for you.

Currently, Microsoft has four versions of BASIC floating around in Windows: Visual Basic, Visual Basic for Applications, WordBasic, and Access Basic. You have already seen the two Visual Basic forms, so this section introduces the other two forms.

WordBasic

You can trace the origin of Visual Basic back to the macro language in Microsoft Word for Windows 1.0. This macro language, WordBasic, included many of the elements found in Visual Basic today. In fact, WordBasic is still the macro language used in Word for Windows 7.0.

This chapter focuses only on Microsoft BASIC development in Windows. Other forms of Microsoft BASIC (for example, QuickBasic) are not discussed.

You can run the Greetings sample in WordBasic, with some slight modifications. As you can see in figure 25.4, Visual Basic inherited its MsgBox function from WordBasic.

Fig. 25.4

Some features of WordBasic are identical to their Visual Basic counterparts.

For the greatest compatibility between Word and VB code, consider using Visual Basic 1.0, or using OLE Automation with the WordBasic type library from VB4.

Usually Visual Basic code is not interchangeable with WordBasic. For a detailed description of WordBasic's syntax, consult the Microsoft WordBasic Help file (WRDBASIC.HLP) in the directory in which you installed Microsoft Word for Windows.

At this point, you might be jumping for joy at the idea that WordBasic and Visual Basic are similar. However, here’s the bad news: Although Visual Basic derives partially from WordBasic, the languages have several differences. In fact, almost every line of code ported between the two products requires some modification. However, all is not lost.

After the tremendous success of Visual Basic, Microsoft realized that it needed a way to enable Visual Basic programmers to customize or control other applications (for example, Word) from VB. Although Word has always exposed WordBasic through Dynamic Data Exchange (DDE), this method was too cumbersome and links were easily broken. Microsoft's solution was to make WordBasic an OLE Automation object. This means that almost any method performed in WordBasic can be executed directly in Visual Basic. Listing 25.1 demonstrates this point.

Listing 25.1 Using WordBasic from Visual Basic

Sub Command1_Click()
'*************************************************
' Create an object variable using the name of
' your choice.
'*************************************************
Dim Word As Object
'*********************************************
' Don't stop for errors.
'*********************************************
On Error Resume Next
'*********************************************
' Assign a WordBasic object to your variable.
'*********************************************
Set Word = CreateObject("Word.Basic")
'*********************************************
' Create a new document and insert some text.
'*********************************************
With Word
.AppShow
.FileNew
.Insert "It Works!"
.MsgBox "See, it works!"
End With
'*********************************************
' If error = 440, then Word couldn't process
' your commands.
'*********************************************
If Err = 440 Then
MsgBox "That command is unavailable", vbCritical
End If
'*********************************************
' When you are done with your object, you can
' reclaim used memory by using the Nothing
' keyword. In this case, the next line really
' isn 't necessary because the memory will
' be recovered when your object goes out of
' scope (at the End Sub).
'*********************************************
Set Word = Nothing

For a complete listing of the WordBasic's OLE Automation syntax, use the WordBasic Type Library with the Object Browser.

For more information about using WordBasic from within Visual Basic, see “Integration with Microsoft Word” later in this chapter.

Access Basic

In late 1992, over a year after the release of Visual Basic, Microsoft released yet another version of BASIC. This version, Access Basic, is the macro language that enables you to customize Microsoft Access. Although there are some subtle differences, Access Basic is about 90 percent compatible with Visual Basic.

Microsoft Access 7.0 includes VBA 2.0, so your Access code must be 100-percent compatible with Visual Basic 4.0. Use the information in this chapter if you want to integrate Access 2.0 with Visual Basic 4.0.

If you declare your constants and remove the line continuation character from your VB code, you can successfully run the Greetings example in Access. Figure 25.5 shows the modified Greetings example in Microsoft Access 2.0.

Fig. 25.5

Code that conforms to the Visual Basic 3.0 syntax is virtually interchangeable with Access Basic.

For the best compatibility between Visual Basic 4.0 (VB4) and Access, you should develop your code in VB4, using the DAO 2.5/3.0 Compatibility layer, and port it to the next version of Access when it becomes available.

Visual Basic 4.0 includes major enhancements to its language that make its code much less compatible with Access Basic 2.0. Although you can create functions that work with either product, you must be more careful when doing so. Be sure to avoid VBA enhancements (for example, line continuation characters, optional arguments, and so on) when writing code for both products, unless you intend to use Access 7.0.

If you want to distribute an application that you have created in Access Basic, Microsoft provides the Access Development Kit (ADK). This product includes additional documentation about Access Basic, and includes everything that you need to distribute your Access application legally to users who do not own Access.

When to Use Macros Rather Than VB

When should use any one of these versions of BASIC? Although there is no one answer, you can use table 25.2 as a guide to help decide which is best for you.

Table 25.2 Choosing the Right Language

Language Usage
Access Basic If you are creating a database application, consider using Access along with the ADK. This solution gives you an enormous amount of "free" functionality, and the users do not have to own Microsoft Access. In addition, Access Basic can control other applications through OLE Automation, so you can do a lot with this powerful product.
Visual Basic If your application requires a tremendous amount of flexibility, or if your application’s users do not own any Microsoft products, Visual Basic is right for you.
Visual Basic for Applications If your application depends on an application that uses VBA as its macro language (such as Excel Project, or Access 95), use VBA. Unless your users do not have one of these products, VBA is probably the right choice for your application. Remember that VBA includes support for OLE Automation, so your possibilities are virtually endless. (For more advice, see the tip that follows this table.)
WordBasic If your application is completely specific to Microsoft Word, use WordBasic. Otherwise, use one of the other three choices and control Word through OLE Automation. Microsoft eventually will update Word to include VBA, so you will have to rewrite any serious development in the future.

If you're developing a custom application and the customer doesn't have Excel or Project, consider buying them a copy. Even the smallest custom projects can cost several thousand dollars, so including one of these products (which you can typically purchase for less than $150) can save you weeks or months of development time.

Integration with Other Microsoft Applications

To harness the full power of Visual Basic, you should take advantage of the existing Microsoft applications installed on your user's computer. This approach saves you from the burden of reinventing the wheel, and enables you to take advantage to the robust features that these products offer.

Communication with Access

At the heart of most custom applications is a database, and Microsoft Access has quickly become one of the most popular programs for database management. This section describes how you can tap the power of Access for use in your Visual Basic applications.

Programmers often ask how their program can control Access. However, this question is rather silly due to the nature of Access. If you own a copy of Visual Basic 3.0 or greater, you already have most of the power of Access. The only part of Access over which you don't have control is its user interface. However, that part of Access isn't very important for custom applications because you create your own user interface with Visual Basic. To help clarify, the next few sections describe some of the components of Access and how you can access them.

Jet Database Layer 2.5

The real power behind Microsoft Access (MSACCESS.EXE) is its database engine, Jet. Everything else in Access is basically a user interface for the Jet. As a Visual Basic programmer, this concept is very important to you because you also have control of the Jet Database Layer (also referred to as data access objects, or DAO). Consider the following scenarios:

These scenarios show some of the most common uses of Access, but your copy of VB.EXE already has much of the power of MSACCESS.EXE to perform these tasks. In most cases, if you really need the Access interface, you shouldn't use Visual Basic.

Communicating with Access through DDE

If you absolutely must communicate with Access directly, you can perform a wealth of operations by using DDE. To connect to an application through DDE, your application must know an application (or service) name, a LinkTopic, and a LinkItem. Access always uses the MSAccess application name. Table 25.3 lists the available topics and items.

Table 25.3 DDE Methods for Microsoft Access 2.0

TopicsItems
System Formats
Status
SysItems
Topics
<Database>
To retrieve available database names, use
LinkTopic = MSAccess|System
LinkItem = Topics
FormList
QueryList
MacroList
ModuleList
ReportList
TableList
<Database>;<TABLE|QUERY> <TableName|QueryName>
To retrieve available tables' names, use
LinkTopic = MSAccess|<database>
LinkItem = TableList
All
Data
FieldCount
FieldNames
FieldNames;T
FirstRow
LastRow
NextRow
PrevRow
SQLText
SQLText;<1 - 255>
<Database>;<SQL> [SQL Statement] All
Data
FieldCount
FieldNames
FieldNames;T
FirstRow
LastRow
NextRow
PrevRow
SQLText
SQLText;<1 - 255>

In addition to the previously mentioned DDE methods, Access also can receive the following commands through LinkExecute:

Using DDE with Microsoft Access 2.0

To demonstrate the power of DDE with Access, this section presents a small application (shown in fig. 25.6) that performs many of the DDE methods previously described.

Fig. 25.6

An example of a DDE tester for Microsoft Access 2.0.

This program is very helpful for testing DDE commands to ensure that they work with Access, before you write a single line of implementation code. Listing 25.2 shows this handy utility’s code, which demonstrates how to implement many of the DDE methods in VB.

Listing 25.2 FRMODE.FRM, an Access 2.0 DDE Test Application

'*************************************************************
' FRMDDE.FRM: User interface for DDE with Access 2.0.
'*************************************************************
Option Explicit
'*************************************************************
' This is the DDE topic name that is used by all DDE connects
' in this demonstration program.
'*************************************************************
Const DDE_APPLICATION = "MSAccess"
'*************************************************************
' These are the indexes of the cbo control array.
'*************************************************************
Const DDE_TOPIC = 0 'cbo(0)
Const DDE_ITEM = 1 'cbo(1)
Const DDE_EXECUTE = 2 'cbo(2)
'*************************************************************
' Arbitrary constants which are used to populate the cbo(1)
' list with valid commands.
'*************************************************************
Const ITEMS_SYSTEM = 0
Const ITEMS_LISTS = 1
Const ITEMS_DATA = 2
'*************************************************************
' This variable stores the path to BIBLIO.MDB file.
'*************************************************************
Private DatabaseName As String
'*************************************************************
' When cbo(0) loses its focus, cbo(1) needs to be updated
' with valid DDE commands.
'*************************************************************
Private Sub cbo_LostFocus(Index As Integer)
Select Case Index
Case DDE_TOPIC
If InStr(cbo(DDE_TOPIC), "System") Then
LoadItems ITEMS_SYSTEM
ElseIf InStr(cbo(DDE_TOPIC), ";TABLE") Then
LoadItems ITEMS_DATA
ElseIf InStr(cbo(DDE_TOPIC), ";QUERY") Then
LoadItems ITEMS_DATA
ElseIf InStr(cbo(DDE_TOPIC), ";SQL") Then
LoadItems ITEMS_DATA
Else
LoadItems ITEMS_LISTS
End If
Case DDE_ITEM
Case DDE_EXECUTE
End Select
End Sub
'*************************************************************
' Either request data, or execute a command.
'*************************************************************
Private Sub cmd_Click(Index As Integer)
Select Case Index
'*****************************************************
' Request Data.
'*****************************************************
Case 0
'*************************************************
' Get the data from Access and close the link.
'*************************************************
DDERequest DDESource, DDE_APPLICATION, _
(cbo(DDE_TOPIC).Text), (cbo(DDE_ITEM).Text)
'*************************************************
' If the data returned doesn't contain a line
' feed, then replace tabs with carriage returns.
'*************************************************
If InStr(DDESource, Chr$(10)) = 0 Then
txtResult = Replace(DDESource, Chr$(9), _
Chr$(13) & Chr$(10))
'*************************************************
' Otherwise display the data as it was received.
'*************************************************
Else
txtResult = DDESource
End If
'*****************************************************
' Execute a Command.
'*****************************************************
Case 1
txtResult = ""
DDEExecute DDESource, DDE_APPLICATION, _
(cbo(DDE_EXECUTE).Text)
End Select
End Sub
'*************************************************************
' Load cbo(0) with some valid topics for Access.
'*************************************************************
Private Sub LoadTopics()
With cbo(DDE_TOPIC)
.Clear
.AddItem "System"
.AddItem DatabaseName
.AddItem DatabaseName & ";TABLE Authors"
.AddItem DatabaseName & ";QUERY [By State]"
.AddItem DatabaseName & ";SQL Select * From Authors"
.ListIndex = 0
End With
End Sub
'*************************************************************
' Load cbo(1) with some valid items for Access, based on a
' specific type of topic.
'*************************************************************
Private Sub LoadItems(TypeOfTopic As Integer)
With cbo(DDE_ITEM)
.Clear
Select Case TypeOfTopic
Case ITEMS_SYSTEM
.AddItem "Status"
.AddItem "Topics"
.AddItem "SysItems"
.AddItem "Formats"
Case ITEMS_LISTS
.AddItem "TableList"
.AddItem "QueryList"
.AddItem "FormList"
.AddItem "ReportList"
.AddItem "MacroList"
.AddItem "ModuleList"
Case ITEMS_DATA
.AddItem "All"
.AddItem "Data"
.AddItem "FieldNames"
.AddItem "FieldNames;T"
.AddItem "FieldCount"
.AddItem "NextRow"
.AddItem "PrevRow"
.AddItem "LastRow"
.AddItem "FirstRow"
.AddItem "SQLText"
.AddItem "SQLText;5"
Case Else
LoadItems ITEMS_SYSTEM
End Select
.ListIndex = 0
End With
End Sub
'*************************************************************
' Loads cbo(2) with some valid Access LinkExecute commands.
'*************************************************************
Private Sub LoadExecutes()
With cbo(DDE_EXECUTE)
.Clear
.AddItem "[OpenDatabase " & DatabaseName & "]"
.AddItem "[CloseDatabase]"
.ListIndex = 0
End With
End Sub
'*************************************************************
' Prepares the form for use. This function is also called
' by txtDatabase_LostFocus to refresh the form.
'*************************************************************
Private Sub Form_Load()
#If Win32 Then
MsgBox "This sample is for Win16 only!", vbCritical
End
#End If
DatabaseName = txtDatabase
LoadTopics
LoadItems ITEMS_SYSTEM
LoadExecutes
txtResult = ""
DDESource = ""
End Sub
'*************************************************************
' Resizes the controls to the size of the form. This function
' is not foolproof, so don't try to break it.
'*************************************************************
Private Sub Form_Resize()
Static Border%
Dim i%
'*********************************************************
' If the form is minimized, then break out.
'*********************************************************
If WindowState = 1 Then Exit Sub
'*********************************************************
' Load the border variable, once.
'*********************************************************
If Not Border Then Border = txtDatabase.Left * 2
'*********************************************************
' Adjust the combo boxes and command buttons.
'*********************************************************
For i = 0 To 2
cbo(i).Width = ScaleWidth - Border
If i < 2 Then cmd(i).Left = cbo(i).Width + _
cbo(i).Left - cmd(i).Width
Next
'*********************************************************
' Adjust the text boxes.
'*********************************************************
txtDatabase.Width = cmd(0).Left - (txtDatabase.Left * 2)
txtResult.Move txtResult.Left, _
txtResult.Top, ScaleWidth - Border, _
ScaleHeight - txtResult.Top - (Border / 2)
End Sub
'*************************************************************
' Updates the database variable and reloads the combo boxes
' to reflect any changes.
'*************************************************************
Private Sub txtDatabase_LostFocus()
DatabaseName = txtDatabase
Form_Load
End Sub
'*************************************************************
' DDE.BAS: An application-independent, reusable module that
' performs DDEExecute and DDERequest methods.
'*************************************************************
'*************************************************************
' Require variable declaration, make text comparisons case-
' insensitive, and declare API functions.
'*************************************************************
Option Explicit
Option Compare Text
#If Win16 Then
Private Declare Function GetModuleHandle Lib "Kernel" _
(ByVal ModuleName As String) As Integer
#End If
'*************************************************************
' Execute one DDE Command.
'*************************************************************
Public Sub DDEExecute(Source As Control, DDEApplication$, _
DDECommand$, Optional DDETopic)
On Error GoTo DDEExecute_Error
'*********************************************************
' If the program isn't running, then exit.
'*********************************************************
If GetModuleHandle(DDEApplication) = 0 Then
MsgBox "This feature requires " & _
UCase$(DDEApplication) & _
".EXE to be running!", vbCritical
Exit Sub
End If
'*********************************************************
' If the optional argument wasn't provided, then assume
' System.
'*********************************************************
If IsMissing(DDETopic) Then DDETopic = "System"
'*********************************************************
' Manual connect, execute the command, and close the link.
'*********************************************************
Source.LinkTopic = DDEApplication & "|" & DDETopic
Source.LinkMode = 2 'Open the Link
Source.LinkExecute DDECommand 'Send the Command
Source.LinkMode 0 'Close the Link
Exit Sub
DDEExecute_Error:
Resume Next
End Sub
'*************************************************************
' Execute a DDE Command.
'*************************************************************
Public Sub DDERequest(Source As Control, DDEApplication$, _
DDETopic$, DDEItem$)
On Error GoTo DDERequest_Error
'*********************************************************
' If the program isn't running, then exit.
'*********************************************************
If GetModuleHandle(DDEApplication) = 0 Then
MsgBox "This feature requires " & _
UCase$(DDEApplication) & _
".EXE to be running!", vbCritical
Exit Sub
End If
'*********************************************************
' Manual connect, request the data, and close the link.
'*********************************************************
Source.LinkTopic = DDEApplication & "|" & DDETopic
Source.LinkItem = DDEItem
Source.LinkMode = 2
Source.LinkRequest
Source.LinkMode = 0
'*********************************************************
' WARNING: If you wish to use a topic such as NextRow,
' you will have to rewrite this program so that
' the link is not broken. NextRow and PrevRow
' require a consistent link in order to cycle
' through the database.
'*********************************************************
Exit Sub
DDERequest_Error:
Resume Next
End Sub
'*************************************************************
' Locate a string within Source, and replace it with
' ReplaceStr.
'*************************************************************
Public Function Replace(ByVal Source$, FindStr$, _
ByVal ReplaceStr$) As String
Dim res%, retStr$
'*********************************************************
' See if the search string exists.
'*********************************************************
res = InStr(Source, FindStr)
'*********************************************************
' While FileStr is in Source, continue to replace it with
' ReplaceStr.
'*********************************************************
Do While res <> 0
retStr = retStr & Left$(Source, res - 1) & ReplaceStr
Source = Mid(Source, res + 1)
res = InStr(Source, FindStr)
Loop
'*********************************************************
' Don't forget to return whatever is left over in source.
'*********************************************************
Replace = retStr & Source
End Function

This sample demonstrates how easily you can communicate with Access 2.0 through DDE. After this book is published, you might find yourself purchasing a copy of Access 7.0. Although the preceding technique should work with Access 7.0, you should consider using OLE Automation instead. For more information on using OLE Automation, see the next section and Chapter 26, “Integration with Microsoft Excel 5.0.”

Integration with Microsoft Word 6.0

Microsoft Word for Windows 6.0 includes a vast amount of functionality that would take a tremendous amount of time to rewrite in Visual Basic. Fortunately, with the release of OLE 2.0 Automation, you can tap into Word's power with relative ease. This section explains how you can make Word work for you.

DDE, the Origin of Communication with Word

Traditionally, if you wanted to tap the power of Microsoft Word, you either wrote a custom application in WordBasic or you used DDE. DDE links are easily broken, so most developers were reluctant to use the power of Word. This problem forced many programmers to rewrite Word's features in Visual Basic, or they simply purchased custom controls with similar functionality.

Today, programmers no longer need to use DDE with Word because Visual Basic 4.0 can access Word with OLE Automation. For this reason, this book doesn’t discuss this dated technology in detail.

You should avoid using DDE, because its future is uncertain. Currently, almost every application that uses DDE is being rewritten to support OLE Automation instead. Therefore, you should use DDE only as a last resort.

OLE Automation with Microsoft Word 6.0

In late 1993, Microsoft released Word for Windows 6.0 with the entire WordBasic engine exposed as an OLE Automation object. Therefore, Visual Basic programmers can now depend on stable connections with Word, and the barriers of the past had been removed. The Speller program shown in figure 25.7 demonstrates the power of this new feature. Listing 25.3 shows the program’s code.

Fig. 25.7

Speller demonstrates the power of OLE Automation with Word.

Listing 25.3 OLE Automation with Word Can Save You Hours of Work

Public Function SpellCheck(ByVal IncorrectText$) As String
Dim Word As Object, retText$
'**************************************************
' OLE Automation always returns errors which can
' usually be ignored.
'**************************************************
On Error Resume Next
'**************************************************
' Create the Object (Word will be opened if it not
' currently running).
'**************************************************
Set Word = CreateObject("Word.Basic")
'**************************************************
' Change the active window to Word, and insert
' the text from Text1 into Word.
'**************************************************
Word.AppShow
Word.FileNew
Word.Insert IncorrectText
'**************************************************
' Perform a spell check.
'**************************************************
' NOTE: Visual Basic will not regain control and
' execute the next line until the spell
' check is complete.
'**************************************************
Word.ToolsSpelling
Word.EditSelectAll
'**************************************************
' Trim the trailing character from the returned text.
'**************************************************
retText = Word.Selection$()
SpellCheck = Left$(retText, Len(retText) - 1)
'**************************************************
' Close Word and return to Visual Basic.
'**************************************************
Word.FileClose 2
Show
'**************************************************
' Recover the memory being used by the Word object.
'**************************************************
Set Word = Nothing
End Function

Unfortunately, not all WordBasic features are available from Visual Basic. For a complete listing of the unsupported features, see page 167 of Programming Integrated Solutions with Microsoft Office (Microsoft Press, 1993).

OLE Automation requires that your application, the server application, and the OLE 2.0 dynamic link libraries (DLL) be loaded. Consequently, OLE Automation applications consume a great deal of random-access memory (RAM) and system resources. You should prepare your program for low-memory situations, which could prevent the program from running under 16-bit Windows. Under 32-bit Windows, low-memory situations rarely occur, but you should still trap for them.

The SpellCheck function provides an easy way to spell check text in your application with only a few lines of code, but this is only the beginning. You can find a listing of all the methods that WordBasic supports in the “Microsoft Word Object” chapter of the ODK's Programming Integrated Solutions book.

To use WordBasic's named arguments in OLE Automation, you must use Visual Basic 4.0 or VBA. Earlier versions of Visual Basic do not support named arguments.

Tips for OLE Automation with Word

When you use OLE Automation with Word 6.0, you should remember the following tips:

Using Word with Custom Controls

By now you should be quite familiar with the reasons that custom controls can be helpful, but with OLE's in-place activation, such controls are an absolute necessity. The tremendous amount of work has gone into making in-place activation usable by Visual Basic programmers, so you certainly want take advantage of it. In VB 4.0, you can support two types of in-place activation for Word:

Each of these methods has advantages and disadvantages. The next few sections demonstrate the power of each tool so that you can decide which is right for you.

Word as an OLE Custom Control

One of the most exciting new features of Visual Basic 4.0 is the capability to use OLE objects as controls on your forms. This capability enables you to use any OLE server as a control in your application. Because Word is such a powerful OLE server, you will certainly want to use this capability.

To experience the simplicity of this powerful feature, just follow these 10 steps:

  1. If it is not already running, start Visual Basic and create a new project.
  2. From choose Tools, Custom Controls.
  3. In the list of available controls in the Custom Controls dialog box (shown in fig. 25.8), click on the Microsoft Word 6.0 Document check box.

Fig. 25.8

Using the Custom Controls dialog box, you can add any OLE object.

  1. Click OK and Visual Basic adds the Word control to your Toolbox. Your Toolbox should now look similar to that shown in figure 25.9

Fig. 25.9

The Toolbox with a Word 6.0 document custom control.

  1. Click the Word control, then click the upper-left corner of Form1. Drag your mouse to the lower-right corner and release the mouse button.
  2. Click the bottom border of your form and increase the height of the form by about half an inch.
  3. Add a new command button to the form and resize it as it is shown in figure 25.10.

Fig. 25.10

This application demonstrates the power of in-place activating OLE custom controls.

  1. With Form1 still active, choose Tools, Menu Editor.
  2. From the Menu Editor, add an invisible top-level menu item called "x" (or whatever else you want). Figure 25.11 shows the properties for this menu item.

Fig. 25.11

The Menu Editor with an invisible menu entry.

  1. Run the application and double-click on the Word object. Your application should look similar to that shown in figure 25.10.

After you activate an OLE object in place, you can deactivate (or close) it only after it loses the focus. The only purpose of the codeless command button in figure 25.10 is to provide an easy way to change the focus.

The form in which you place the control must have either a visible or invisible menu. If it does not, the activated object cannot display its menu in your application.

In addition to the functionality shown earlier, the Word document control supports the following properties and events:

• DragDrop event

• DragIcon property

• DragMode property

• DragOver event

• GotFocus event

• Height property

• HelpContextID property

• Index property

• Left property

• LostFocus event

• Name property

• TabIndex property

• TabStop property

• Tag propery

• Top property

• Visible property

• WhatsThisHelpID property

• Width property

As you can see, the Word document control (and any other OLE controls) support a wealth of events and properties. In some cases, this method possesses all the functionality that you need.

If you plan to use only Word in your application, the Word document control is a great choice. However, if your users need to insert objects at run time, consider using the OLE container control.

Your application fails if the destination system does not have Microsoft Word 6.0, or if Word is not registered in the system registry properly.

OLE Container Control with Microsoft Word 6.0

One of the major enhancements for Visual Basic 3.0 was its support for OLE 2.0. Unfortunately, because no major OLE 2.0 applications existed at that time, Visual Basic 3.0’s OLE container control shipped with several limitations.

Now that people are starting to use OLE 2.0, Microsoft has released a more advanced version of this control. Some of the benefits of this updated control are the following:

The container control gives you the power to create the program shown in figure 25.10, and you also have more control of your inserted object. Figure 25.12 and listing 25.4 shows a sample application that adds a full-featured word processor to your application with very little code (except for comments).

Fig. 25.12

OLECONT.VBP demonstrates in-place activation.

The first file in the OLECONT.VBP is PUBLIC.BAS. It keeps track of all the Public (or Global) constants, variables, and functions for this project. Because this application uses a multiple document interface, you have to store your variables that keep track of the open forms in a module that all the project’s forms can access. This module also contains a public function, UpdateStatus, that gives any procedure in the project the capability to update the status bar.

Listing 25.4 OLECONT.VBP, an OLE Container Control with Word Embedded

'*****************************************************************
' PUBLIC.BAS - Global constants, functions, and variables
'*****************************************************************
Option Explicit
'*****************************************************************
' These globals keep track of the new instances of frmWord
'*****************************************************************
Public Const MAX_WINDOWS = 4
Public WinWord(MAX_WINDOWS) As New frmWord
Public WordWindows As Integer
'*****************************************************************
' Generic update status bar routine
'*****************************************************************
Public Sub UpdateStatus(StatusBar As Label, Optional StatusText)
If IsMissing(StatusText) Then
StatusBar = "Ready"
Else
StatusBar = StatusText
End If
End Sub

The second file in this project is the MDI parent form, mdiOLE. This form is responsible for controlling the status bar, toolbar, and its menu.

'*****************************************************************
' MDIOLE.FRM - MDI Parent Form
'*****************************************************************
Option Explicit
'*****************************************************************

The application stores the toolbar buttons in a image control array, imgTools. You set the picture’s property and the control’s position in the array at design time. The odd controls in the array contain the up picture, and the even (odd number + 1) controls contain the down picture. The imgHold image control is a temporary location to store the toolbar picture when the user clicks a toolbar button.

When an odd-numbered image control receives a Mouse_Down event, the application stores its image in a holding image control, imgHold. Next, the application sets the imgTools(Index) picture property to the picture of the next control in the array (index + 1), which should be its down picture. Finally, when the control receives a Mouse_Up event, the application restores the control’s up picture by setting imgTools(Index) to the picture currently stored in imgHold. The following code shows how to accomplish this:

' Saves the button image in imgHold, and inserts the down picture
'*****************************************************************
Private Sub imgTools_MouseDown(Index As Integer, _
Button As Integer, Shift As Integer, X As Single, _
Y As Single)
imgHold.Picture = imgTools(Index).Picture
imgTools(Index).Picture = imgTools(Index + 1).Picture
End Sub
'*****************************************************************
' Restores the graphic, and closes the application
'*****************************************************************
Private Sub imgTools_MouseUp(Index As Integer, _
Button As Integer, Shift As Integer, _
X As Single, Y As Single)
imgTools(Index).Picture = imgHold.Picture
Unload Me
End Sub

Placing Action Code

Because this toolbar has only one tool, you place its code in the Mouse_Down event. If this toolbar had more than one button, you would place the action code for the imgTools control array in a large Select statement in the Click event.

Every time that the user moves the mouse over a toolbar button, you should update the status bar to reflect the action that the tool performs. Listing 25.5 demonstrates how to do this.

Listing 25.5 Updating the Status Bar

'*****************************************************************
' Updates the status bar
'*****************************************************************
Private Sub imgTools_MouseMove(Index As Integer, _
Button As Integer, _
Shift As Integer, X As Single, _
Y As Single)
UpdateStatus lblStatus, "Closes " & Caption
End Sub

As listing 25.6 demonstrates, the MDIForm_Load procedure maximizes the window and tiles all open child windows, and the MouseMove procedures set the caption of lblStatus equal to "Ready" whenever the user moves the mouse over the MDI form.

Listing 25.6 Preparing OLECONT.VBP for Use

'*****************************************************************
' Prepares the application for use
'*****************************************************************
Private Sub MDIForm_Load()
WindowState = 2
frmWord.Show
Arrange vbTileHorizontal
End Sub
'*****************************************************************
' Updates the status bar with the default text
'*****************************************************************
Private Sub MDIForm_MouseMove(Button As Integer, _
Shift As Integer, _
X As Single, Y As Single)
UpdateStatus lblStatus
End Sub
'*****************************************************************
' Updates the status bar with the default text
'*****************************************************************
Private Sub Toolbar_MouseMove(Button As Integer, _
Shift As Integer, _
X As Single, Y As Single)
UpdateStatus lblStatus
End Sub
'*****************************************************************
' Updates the status bar with the default text
'*****************************************************************
Private Sub StatusBar_MouseMove(Button As Integer, _
Shift As Integer, _
X As Single, Y As Single)
UpdateStatus lblStatus
End Sub

To make this code application-independent, you create separate procedures for Highlight and HighlightBar. You can then use these procedures in this project, but also copy and paste them into another project.

Listing 25.7 adds a three-dimensional appearance to the status bar and the toolbar.

Listing 25.7 Adding a Three-Dimensional Appearance to the Status Bar and Toolbar

'*****************************************************************
' Adds a 3-D appearance to the status bar
'*****************************************************************
Private Sub StatusBar_Paint()
HighlightBar StatusBar
Highlight lblStatus
End Sub
'*****************************************************************
' Adds a 3-D appearance to the toolbar
'*****************************************************************
Private Sub Toolbar_Paint()
HighlightBar Toolbar
End Sub

Listing 25.8 shows two functions that demonstate how to use a series of line methods to create a three-dimensional effect around controls.

Listing 25.8 Adding a Three-Dimensional Appearance to Controls

'*****************************************************************
' Adds a 3-D effect to a picture box
'*****************************************************************
Private Sub HighlightBar(Bar As PictureBox)
Bar.Line (0, 5)-(Bar.ScaleWidth, 5), vb3DLite
Bar.Line (0, Bar.ScaleHeight - 15)-(Bar.ScaleWidth, _
Bar.ScaleHeight - 15), vb3DShadow
End Sub
'*****************************************************************
' Adds a 3-D border around a control
'*****************************************************************
Private Sub Highlight(Object As Control)
Const HORIZONTAL_OFFSET = 50
Const VERTICAL_OFFSET = 70
'*****************************************************
' Top
'*****************************************************
StatusBar.Line (Object.Left - HORIZONTAL_OFFSET, _
Object.Top - HORIZONTAL_OFFSET)- _
(Object.Width, _
Object.Top - HORIZONTAL_OFFSET), _
vb3DShadow
'*****************************************************
' Left
'*****************************************************
StatusBar.Line (Object.Left - HORIZONTAL_OFFSET, _
Object.Top - HORIZONTAL_OFFSET)- _
(Object.Left - HORIZONTAL_OFFSET, _
Object.Height + VERTICAL_OFFSET), _
vb3DShadow
'*****************************************************
' Bottom
'*****************************************************
StatusBar.Line (Object.Left - HORIZONTAL_OFFSET, _
Object.Height + VERTICAL_OFFSET)- _
(Object.Width, _
Object.Height + VERTICAL_OFFSET), _
vb3DHiLight
'*****************************************************
' Right
'*****************************************************
StatusBar.Line (Object.Width, _
Object.Top - HORIZONTAL_OFFSET)- _
(Object.Width, _
Object.Height + VERTICAL_OFFSET + 15), _
vb3DHiLight
End Sub

Listing 25.9 shows FRMWORD.FRM, an MCI child form with an OLE container control.

Listing 25.9 FRMWORD.FRM, an MCI Child Form with an OLE Container Control
'*****************************************************************
' FRMWORD.FRM - MDI Child form with an OLE container control.
'*****************************************************************
Option Explicit
'*****************************************************************
' This ensures that the Word object is always the same size as the
' client area of the window.
'*****************************************************************
Private Sub Form_Resize()
Word.Move 0, 0, ScaleWidth, ScaleHeight
End Sub
'*****************************************************************
' Handles clicks from the File submenu
'*****************************************************************
Private Sub mnuFileItems_Click(Index As Integer)
On Error Resume Next
Select Case Index
Case 1 'New
If WordWindows <= MAX_WINDOWS Then
WordWindows = WordWindows + 1
WinWord(WordWindows - 1).Caption = Me.Caption _
& " -" & Str$(WordWindows + 1)
End If
Case 2 'Open...
mdiOLE!cdlg.InitDir = App.Path
mdiOLE!cdlg.Flags = cdlOFNFileMustExist + _
cdlOFNHideReadOnly + cdlOFNNoChangeDir
mdiOLE!cdlg.ShowOpen
If Err = cdlCancel Then Exit Sub
Open (mdiOLE!cdlg.FileName) For Binary As #1
Word.ReadFromFile 1
Close #1
Case 3 'Save As...
mdiOLE!cdlg.Flags = cdlOFNOverwritePrompt + _
cdlOFNNoChangeDir
mdiOLE!cdlg.ShowSave
If Err = cdlCancel Then Exit Sub
Open (mdiOLE!cdlg.FileName) For Binary As #1
Word.SaveToFile 1
Close #1
Case 5
mdiOLE!cdlg.Flags = cdlPDDisablePrintToFile + _
cdlPDNoPageNums + cdlPDNoSelection
mdiOLE!cdlg.ShowPrinter
If Err = cdlCancel Then Exit Sub
With Word
.DoVerb vbOLEShow
Printer.PaintPicture .picture, 0, 0
.Close
Printer.EndDoc
End With
Case 7 'Exit
Unload mdiOLE
End Select
End Sub'*****************************************************************
' Updates the Object submenu's enabled status
'*****************************************************************
Private Sub mnuObject_Click()
mnuObjectItems(1).Enabled = Not (Word.OLEType = vbOLENone)
mnuObjectItems(2).Enabled = Not (Word.OLEType = vbOLENone)
mnuObjectItems(3).Enabled = Word.PasteOK
mnuObjectItems(4).Enabled = Word.PasteOK
mnuObjectItems(5).Enabled = Not (Word.OLEType = vbOLENone)
End Sub'*********************************************************************
' Handles clicks from the Object Submenu
'*********************************************************************
Private Sub mnuObjectItems_Click(Index As Integer)
Select Case Index
Case 1 'Cut
With Word
.DoVerb vbOLEShow
.Copy
.Close
.Delete
End With
Case 2 'Copy
With Word
.DoVerb vbOLEShow
.Copy
End With
Case 3 'Paste
Word.Paste
Case 4 'Paste Special...
Word.PasteSpecialDlg
Case 5 'Delete
Word.Delete
Case 7 'Close Object
Word.Close
End Select
End Sub'*****************************************************************
' Updates the status bar
'*****************************************************************
Private Sub Word_MouseMove(Button As Integer, Shift As Integer, _
X As Single, Y As Single)
UpdateStatus mdiOLE!lblStatus, _
"Double click to edit this object in Word"
End Sub
'*****************************************************************
' Handles clicks from the Window submenu
'*****************************************************************
Private Sub mnuWindowItems_Click(Index As Integer)
mdiOLE.Arrange Index - 1
End Sub
'*****************************************************************
' Displays a Open dialog, and loads the file into an OLE container
'*****************************************************************
Private Sub OLEOpenFile(OLEObject As OLE)
Dim iFile%
mdiOLE!cdlg.InitDir = App.Path
mdiOLE!cdlg.Flags = cdlOFNFileMustExist + cdlOFNHideReadOnly _
+ cdlOFNNoChangeDir
mdiOLE!cdlg.ShowOpen
If Err = cdlCancel Then Exit Sub
iFile = FreeFile
Open (mdiOLE!cdlg.FileName) For Binary As iFile
OLEObject.ReadFromFile iFile
Close iFile
End Sub
'*****************************************************************
' Displays a Save As dialog and saves contents of an OLE container
'*****************************************************************
Private Sub OLESaveFile(OLEObject As OLE)
Dim iFile%
mdiOLE!cdlg.Flags = cdlOFNOverwritePrompt + NoChangeDir
mdiOLE!cdlg.ShowSave
If Err = cdlOFNCancel Then Exit Sub
iFile = FreeFile
Open (mdiOLE!cdlg.FileName) For Binary As iFile
OLEObject.SaveToFile iFile
Close iFile
End Sub

As this application demonstrates, the power of the OLE container control can yield some amazing results. In addition, you can spare yourself from from hundreds of hours of coding by using this powerful feature.

From Here...

This chapter demonstrated how powerful Visual Basic can be when you integrate Office applications, so now it is time to explore more ways to use Microsoft Office products in your programs. The next chapter looks in detail at the most powerful Office product, Microsoft Excel, and demonstrates how easily you can use a VBA-aware application such as Excel with Visual Basic 4.0 to create world-class applications.

To learn more about related topics, see the following sections:


© 1996, QUE Corporation, an imprint of Macmillan Publishing USA, a Simon and Schuster Company.