How to Use Word 97 as a Report Engine

Revised: October 25th, 1999

by Dr Allan Harkness - A.Harkness@bio.gla.ac.uk

Introduction

Microsoft has recently been advertising their monolithic Word 97 as a report engine for their development tools (see 'Using Word as a Report Generation Component'). The examples they give use OLE Automation. There seems to be no good reason why Delphi cannot do the same, particularly with the loss of ReportSmith (no loss…) and the problems with QuickReport (was quite good once, then things went astray…).

There have been several small components released recently that purport to be some form of report engine using Word 97. However, they are rather minimalist in nature and all seem to use late binding. The reason for this will become clear later.

Hidden away in one of the Delphi demos (..\Delphi4\Demos\Activex\Oleauto\Word8) is a rather simple "report engine". It has a full implementation of the events generated by Word, but only a few methods. However, it uses early binding.

There are several reasons why early binding has not taken off with Word:

  1. Many are still put off by the difficulties they had with type libraries from Delphi 3
  2. Word's objects make great use of optional parameters. Early binding forbids their use. Borland were not very forthcoming with a method for passing 'null' optional parameters in Delphi 3 (see Appendix 1) and didn't exactly advertise the existence of the EmptyParam 'constant' in Delphi 4.
  3. Many arguments are enumerate types (TOleEnum). You need to constantly look these up in Word VBA help.
  4. Far too many arguments are of type OleVariant. These require that you pass a variable of OleVarient type, i.e., you cannot do something like Selection.MoveLeft(wdWord, 1, wdMove) as these arguments are constants, not OleVariants.
  5. Code for getting the current word application using late binding was frequently give in the OleAutomation newsgroups (see Appendix 2). This is useful; otherwise you end up with loads of copies of word.

Here are a few reasons why early binding is better:

  1. We all know it is faster
  2. It is language independent
  3. You get tool tips to show you all 11 parameters required to do ActiveDocument.SaveAs (…)
  4. You are guaranteed to get a Word 97 object. If you use CreateOLEObject('Word.Application') then you may get an error if an older version of word is present as there was no Application object then (you would have used Word.WordBasic). You may still get this error if you have both Word 97 and a previous version. This is very important if you application is distributed to unknown machines (with goodness knows what on them).
  5. You can interface with the events generated from Word (for what it's worth).

 

Methods

With these considerations in mind, I decided to create a set of classes that will allow me to interact with word and rarely have to deal with the unpleasantries of COM. I took as my starter, the Word97.pas file in the Delphi demos.

If you look at this file, you will see how to get round problem 4, i.e., the OleVariants. You need to typecast all the arguments as OleVariants and then pass these to the COM method. All I needed was to flesh out the classes with some more methods and properties.

Delphi's demo uses the selection object to insert text into word. This allows you to see what happens and where. However, moving the selection around is slow as the screen is constantly updated and if you have a big document, it requires scrolling the page. The best way of working with regions of text is to use ranges. You can get a range object from many Word objects, including the selection object itself and bookmarks.

I think bookmarks are the ideal way of identifying where to insert text into a document. You can assign bookmarks to text or even just a point in the document. The best place to do this is in the template. Then when you make a new document, you just move to all the bookmarks adding the appropriate text as necessary.

In addition to the TWordApp object, I have implemented two other objects: TWordDoc and TWordRange.

Results: Demo and Demo2

The Demo.dpr project gives a quick example of how to open a document and move between bookmarks to fill in the details. It also shows the events in action (Delphi 4&5). The program opens a Word document but you can simply alter this to create a document from a template, where the template has the relevant bookmarks defined.
The demo also demonstrates using macros to control the Word menus. I have overwritten the File.New menu item so that it displays the template directory in "details" view rather than "large icon" view. Of more use, I have overwritten the File.Exit menu item to ask if the user is sure he wants to close Word internally rather than via Delphi (you could simply make this not possible). If the user presses Alt-F4 to close Word, this also runs the macro associated with File.Exit. If the user clicks the main Word [x] button or choses close from the system menu, this macro is not run. However you can disable this system menu item, which in turn disables the [x] button, as shown in the demo. Using these techniques, it is quite difficult for a user to close Word without you knowing, other than writing macros or killing the process.
Note 1: when you load my test.doc file from within Delphi, Word does not complain about macros being present (an anti-virus feature) whereas it will if you load the document from within a standalone version of Word. This appears to be a security bug in Word.
Note2: the code to disable the system close menu item grabs a Word application, not necessarily your Word application (if you have more than one running). I am not sure how you get the window handle of the specific Word application you are using but I suspect it either the most recently created or the active one. It would make sense to grab this handle after activating Word for the first time.

The Demo2.dpr project shows the exact same routine but opens a document inside a TOleContainer. In this case I have not made menus to merge - perhaps someone could jazz up this demo. I think it is enough that I got TOleContainer to work! Of interest, Word does complain about macros in documents loaded into a TOleContainer (so I use a different doc file). However in this case there is less need for them as you can control menus to a greater extent here. You could move the macros to a template to avoid this anti-virus message.

Discussion

I have made extensive use of Delphi 4's default parameters. These allow you almost as much freedom as Visual Basic's default parameters—although you cannot use named parameters and you can't do TWordRange.Move(,1).

Quamar Ali converted the original Word97 for Delphi 3. I have also made a few alterations to this version. In particular, I have thrown out the sink (if anyone has Delphi 3 and the Borland demos come with a Word97, let me know if the sink is there and I will think about putting it back in). The Delphi 3 version is still quite useable, despite the lack of default parameters. There is one additional task you must perform to get these classes to work for Delphi 3. When Delphi 4 imports the Word type library, it converts 'Application' to 'Application_' as it considers 'Application' as a reserved word (it is used extensively in the project file and is defined in forms.pas). Delphi 3 is not so clever. Since you will only need to import this library once, you can ignore the warning at the start of the Word_TLB.PAS file saying that any changes will be overwritten. Search for all instances of 'Application' and change to 'Application_' (NB this is the object not the class type). You will find this file in the ../Delphi 3/Imports directory... To help you I now provide the amended Word_TLB.pas for Delphi 3 to download from my website (you will be glad to hear that it compresses signficantly).

Using these objects in my programs allows me to concentrate on what I want to do with Word without having the hassle of COM. I would welcome additions to the unit. Bear in mind that Word_TLB.pas is 860k in size and my unit is already 45k. It probably has too much as it stands (I don't really use the selection methods in TWordApp), so any additions must be really worthwhile. If you would like to download the demo code... see the link at the bottom of this article.

I use Word to generate letters with English text, generated from data in an Access database (I use Diamond Access to connect to this). This is rather different from a tabular report. A standard report component would be better for in that instance (e.g., ReportBuilder). The advantage of Word is that you can edit and save the letters as though they were typed-in letters. It is perhaps a bit on the large side if all you want is a list.

The current version (1.8) was released 25/10/1999.

Appendix 1

How to make your own EmptyParam for Delphi 3. Declare the following as a global variable in a unit:

var
 EmptyParam: OleVariant;
At the end of the unit, put these lines in the initialization section
initialization
  TVarData (EmptyParam).VType  := varError;
  TVarData (EmptyParam).VError := DISP_E_PARAMNOTFOUND;
end.

 

Appendix 2

How to get the current Word application or else create one, using late binding:

var
  wrdApp : OleVariant;
begin
  try
    wrdApp := GetActiveOLEObject('Word.Application');
  except on EOleSysError do
    wrdApp := CreateOLEObject('Word.Application');
  end;

 

Appendix 3

Download Delphi 4 source and demo

Download Delphi 3 source and demo

Appendix 4

Other Word 97 and Delphi resources:


MS Word Articles - http://www.microsoft.com./WordDev/w-a&sa.htm
Microsoft Office 97 Automation Help File Available on MSL - http://support.microsoft.com/support/kb/articles/Q167/2/23.asp

Other COM programmers are Graham Marshall, Joel Milne, Binh Ly and Deborah Pate
They all know far more about Delphi and COM than I do and frequent the delphi OleAutomation newsgroup.

Graham Marshall's Delphi /Word / Excel page is http://vzone.virgin.net/graham.marshall/default.htm

Joel Milne's Delphi / Word page is http://www.softmosis.ca/WordFAQ.html

Binh Ly's Delphi / COM page is http://www.techvanguards.com/com/com.htm

Deborah Pate's Delphi Automation page is http://www.djpate.freeserve.co.uk/Automation.htm