Microsoft Office Automation with Visual FoxPro

Fundamental operations

No matter which server you re dealing with, there are certain operations you need: opening the server, closing the server, opening and closing a document, checking whether the server is open, checking whether you have a document to work with, and so forth. Methods and properties to handle these operations are added to cusWrapServer, though some of the methods are abstract because they require server-specific code. Table 1 shows the custom properties of cusWrapServer. Table 2 shows the custom methods of the class.

Table 1. Properties of cusWrapServer. These properties are added to the abstract wrapper class.

Property

Type

Description

cServerName

Character

The name of the Automation server, for example, "Word.Application".

lOpenAsNeeded

Logical

Indicates whether to open the server when a method is called that requires its use. If this is .F., some methods may fail.

oDocument

Object

Object reference to the active document. "Document" here is used in the broad sense to refer to the primary type of object used by the application.

oServer

Object

Object reference to the server.

Table 2. Methods of cusWrapServer. These custom methods are added to the abstract class so they re available to all wrapper classes. Many of them are abstract at this level; that is, the code to be executed must be specified in subclasses.

Method

Description

CloseDocument

Close a document of the primary type for the server. Abstract.

CloseServer

Close the server instance. Abstract.

HideServer

Make the server instance invisible. Abstract

IsDocOpen

Indicates whether there s a document open. Abstract.

IsServerOpen

Indicates whether there s an instance of the server available. Abstract.

NewDocument

Creates a new document of the primary type for the server. Abstract.

oDocument_Access

Returns a reference to the active document.

OpenDocument

Opens a document of the primary type for the server.

OpenServer

Opens an instance of the server.

OpenServerDocument

Server-specific portion of OpenDocument. Handles server-specific details. Abstract.

SaveDocument

Saves a document of the primary type for the server.

ShowServer

Makes the server visible. Abstract.

TellUser

Displays a message to the user.

zReadme

Documentation method.

Talking to the user

All communication with the user is sent through a single method, TellUser. This provides centralized message services for the wrapper class. In the version of cusWrapServer provided, TellUser is quite simple (and is really aimed more at the developers using the wrapper class than at end users). It uses either a WAIT WINDOW or a basic message box and doesn t accept any input. While this version is fine for simple testing, you ll probably want to replace this method with one that ties into the message services already in use in your applications.

* cusWrapServer.TellUser

* Pass a message along to the user. By sending all messages through this

* method, it's easy to change the mechanism used for messages.

* This is just a very basic message handler.

LPARAMETER cMessageText, lDontWait

IF SYS(2335) = 1 ; && Not in unattended mode

AND This.Application.StartMode = 0 && Normal VFP interactive session.

IF lDontWait

WAIT WINDOW cMessageText NOWAIT

ELSE

MessageBox( cMessageText )

ENDIF

ELSE && Can't generate a UI dialog

ERROR cMessageText && generates error 1098

* You may prefer to use COMReturnError() here instead.

ENDIF

RETURN

Because every method that communicates with the user calls on TellUser, it would be quite easy to substitute a more sophisticated messaging mechanism. One obvious change is to offer the option of "silent messaging," returning error codes rather than displaying messages.

Creating a server instance

Probably the hardest, most error-prone part of Automation is getting a server started. The server might be missing or not registered, or Windows may be unable to start it for some reason. To deal with all these possibilities, cusWrapServer uses a separate class whose sole purpose is to instantiate an Automation server. This class, called cusOpenServer, is also derived from Custom. Although the methods in cusOpenServer could be included in cusWrapServer, it makes sense to keep them in a separate class because many processes need to open an Automation server. Rather than forcing them all to load the entire abstract wrapper class, which has many methods unrelated to the task of opening a server, they can use just cusOpenServer.

cusOpenServer has two custom properties and two custom methods. The cServerName property holds the name of the server to be instantiated; the oServer property holds the reference to the newly-created server. The OpenServer method does the actual work of creating the server. The zReadMe method of the class is a documentation method (as in all the classes presented here).

The Init method of cusOpenServer accepts two parameters, the name of the server to instantiate and a logical value indicating whether to instantiate it right away or wait until OpenServer is called explicitly. It then uses the Registry class (provided as a VCX with VFP 6 and as a PRG with VFP 5) to make sure the server is registered. If not, Init returns .F., which prevents this object (cusOpenServer) from being created. Here s the code for cusOpenServer.Init:

* cusOpenServer.Init

* Check whether the specified server name is valid and,

* if the user specified, create the server

LPARAMETERS cServerName, lCreateAtInit

LOCAL oRegistry

IF VarType(cServerName) = "C"

* Check to see if this is a valid server name

* In development/testing, you may need to add the

* Registry.VCX from HOME()+"\FFC\" to your path

oRegistry = NewObject("Registry","Registry")

IF oRegistry.IsKey(cServerName)

This.cServerName = cServerName

ELSE

RETURN .F.

ENDIF

RELEASE oRegistry

ELSE

* No point in creating this object

RETURN .F.

ENDIF

IF VarType(lCreateAtInit) = "L" AND lCreateAtInit

This.OpenServer()

ENDIF

RETURN

The OpenServer method of cusOpenServer instantiates the server and preserves the reference. Here s the code:

* cusOpenServer.OpenServer

* Open the server specified by This.cServerName

* Store the new instance to This.oServer

IF VARTYPE(This.cServerName) = "C"

This.oServer = CreateObject(This.cServerName)

ENDIF

RETURN This.oServer

There are actually some other problems that can occur when you attempt to instantiate the server. They re discussed in Chapter 14, "Handling Automation Errors." In an attempt to keep this code easy to read, we haven t dealt with all of them here. If you use this class as a basis for your own wrapper class, be sure to integrate the code from Chapter 14 into this Init method.

The OpenServer method of cusWrapServer instantiates cusOpenServer, passing .F. for the lCreateAtInit parameter. If cusOpenServer is successfully instantiated, OpenServer retrieves the reference to the new server and stores it in the oServer property. If not, the user is informed that the server couldn t be started.

* cusWrapServer.OpenServer

* Create an instance of the Automation server

* and store a reference to it in This.oServer

LOCAL oGetServ

IF EMPTY(This.cServerName)

This.TellUser("No server specified")

RETURN .F.

ENDIF

oGetServ = NewObject("cusOpenServer","Automation","",This.cServerName,.F.)

IF VarType(oGetServ) = "O"

This.oServer = oGetServ.OpenServer()

IF VarType(This.oServer)<>"O"

This.TellUser("Can't create automation server")

RETURN .F.

ENDIF

ENDIF

RETURN

Closing the server

Unlike creating instances of servers, the method for shutting down the different servers varies. In particular, you need to take different actions to be sure that the user doesn t see a dialog when you attempt to close the server application. So, the CloseServer method is left empty (abstract) at the cusWrapServer level and must be coded in each concrete subclass.

However, we want to be sure not to leave server applications running when we shut down our wrapper class. So, the Destroy method contains this code:

* cusWrapServer.Destroy

* Close the server on the way out, if necessary,

* so we don't leave it running in the background.

IF This.IsServerOpen()

This.CloseServer()

ENDIF

RETURN

Opening a document

The term "document" in Windows is used to mean the primary type of file on which an application operates. In Excel, it s a workbook, and in PowerPoint, it s a presentation. Word confuses the issue, because its primary file type is also called a document. The remainder of this chapter uses the term "document" in the Windows sense, rather than meaning a Word file.

Much of what you need to do to open a document is the same regardless of the application. You need to make sure the user specified a document to open, make sure the document exists, and make sure the server is open or, if appropriate, open the server. However, the actual process of opening a document varies for the individual servers. The wrapper class divides the code into these two parts. The OpenDocument method does everything it can, then calls the OpenServerDocument method, which is coded at the subclass level. Here s the code for OpenDocument:

* cusWrapServer.OpenDocument

* Open a specified document and make it the active document.

* Return a logical value indicating whether the document

* was successfully opened.

LPARAMETER cDocument

* cDocument = name of document to be opened including FULL PATH

LOCAL lIsServerOpen, lSuccess

* Check whether parameter was passed and is valid

IF VarType(cDocument)<>"C"

This.TellUser("Must specify document to open")

RETURN .F.

ENDIF

IF NOT FILE(cDocument)

This.TellUser("Can't find " + cDocument)

RETURN .F.

ENDIF

* Make sure server is open

lIsServerOpen = This.IsServerOpen()

DO CASE

CASE NOT lIsServerOpen AND NOT This.lOpenAsNeeded

This.TellUser("Program is not available")

RETURN .F.

CASE NOT lIsServerOpen

* Open server

This.OpenServer()

OTHERWISE

* Server is open and all is well

ENDCASE

* Attempt to open document

* Call server specified method

lSuccess = This.OpenServerDocument( cDocument )

RETURN lSuccess

Saving a document

The techniques for saving documents are similar enough in the Office servers that the SaveDocument method can be defined in the abstract class. This method accepts two parameters, an object reference to the document to be saved and the filename, including the full path, where it s to be saved. Both parameters are optional. If the document reference is omitted, the active document is saved. If the filename is omitted, the file is re-saved to its current location. As written, the method overwrites existing files without informing the user.

* cusWrapServer.SaveDocument

* Save the specified document. If a filename is passed,

* save it with that filename. Otherwise, use the name it

* already has. If it's a new document and no name is

* specified, fail.

* DOES NOT CHECK FOR OVERWRITE OF FILE

* If no document is passed, use the active document.

LPARAMETERS oDocument, cFileName

LOCAL lReturn, cOldCentury

IF VarType(oDocument) <> "O"

oDocument = This.oDocument

ENDIF

DO CASE

CASE IsNull(oDocument)

* No document to save

* Tell the user and get out

This.TellUser("No document to save.")

lReturn = .F.

CASE VarType(cFileName) = "C"

* Just save it with the specified name

lReturn = .T.

oDocument.SaveAs( cFileName )

tFDateTime = This.GetFileDateTime( cFileName)

IF NOT FILE(cFileName) OR NOT (tFDateTime + 1 > DATETIME())

* If file doesn't exist or wasn't updated in the last second

This.TellUser("Couldn't save document as " + cFileName)

lReturn = .F.

ENDIF

CASE VarType(cFileName) = "L" AND NOT (oDocument.Name == oDocument.FullName)

* No name, but previously saved, so save with current name

lReturn = .T.

oDocument.Save()

cFileName = oDocument.FullName

tFDateTime = This.GetFileDateTime( cFileName)

IF NOT (tFDateTime + 1 > DATETIME())

* If file wasn't updated in the last second

This.TellUser("Couldn't save document")

lReturn = .F.

ENDIF

OTHERWISE

This.TellUser("Specify filename")

lReturn = .F.

ENDCASE

RETURN lReturn

This method works for Word and PowerPoint. Excel doesn t let you use SaveAs to overwrite an existing file without prompting the user, so the Excel-specific subclass contains its own version of SaveDocument that works around this annoyance.

The GetFileDateTime method used here returns the time the file was last saved as a datetime value. We were surprised to have to resort to this round-about method of checking for a successful save, but surprisingly, the Save and SaveAs methods don t return a value to indicate whether they were able to save the file. In a network situation, this test can fail even though the file was saved if the clock on the server is out of synch with the clock on the local machine.

Other basic operations

Although there are other methods that are clearly needed for every server, such as printing or closing the server application, they have to be defined in subclasses because they depend on specific properties and methods of the servers, or the way of doing them is too different from one server to the next.

Copyright 2000 by Tamar E. Granor and Della Martin All Rights Reserved

Категории