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:
Here are a few reasons why early binding is better:
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 parametersalthough 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