Word 97 Frequently Asked Questions

Last updated 7th December 1999

FAQ Questions

[Warning] AHWord97.pas (linexxx): Constant expression violates subrange bounds

Why have you changed Word97.pas to AHWord97.pas?

How do I disable the [x] close button and the equivalent system menu item in Word?

I want to re-define what Word does for x menu item (e.g., FileNew)

Delphi 5 comes with the Word objects as components - surely your classes are no longer needed?

[Fatal Error] AHWord97.pas(linexxx): File not found: 'Word_TLB.dcu'   (or 'Word97.pas' in Delphi 5)

[Error] AHWord97.pas(linexxx): Undeclared identifier: 'Application_'
[Error] AHWord97.pas(linexxx): Undeclared identifier: 'CoApplication_'

Where is the Borland demo version of Word97?

Which versions of Delphi can use these classes?

Is there a C++ Builder version of these classes?

Why use early binding?

I'm odd - I would like to use late binding. How to I get a handle on Word?

Delphi 3 has no EmptyParam, how can I survive?

Why is Word slow to load and eats vast amounts of memory?

Automating Word is faster if I drop down a Word menu — How can I get this speed advantage in code?

I want to use Word to produce xxx, what is the best way to do this?

How do I use Word inside the TOleContainer object?

I want to convert my Word macros/VBA code to Delphi as Delphi is compiled and therefor fast

How do I pass parameters to Word macros?

Where can I get more information on Word automation and Delphi COM in general?

FAQ Answers

[Warning] AHWord97.pas (linexxx): Constant expression violates subrange bounds

This is a issue that comes up with a few constants imported from the type library of MS Office products. While usually you should take heed of any warnings by the compiler, you can safely ignore these ones. Your program will run as you expect it to. Continue to use the constants as they help document your code.

 

Why have you changed Word97.pas to AHWord97.pas?

The new Delphi 5 server controls for Word are in a file called Word97.pas (in C:\Program Files\Borland\Delphi5\Ocx\Servers)
This file is both the imported type library and the new Delphi component shell. I have a compiler IFDEF to use this file rather than having to import Word_TLB.pas in Delphi 5. You can still do this if you want (you will need to amend the uses clause of my classes). However I would hope the compiler is clever enough to remove the component shell from your final program if you use Delphi's Word97.pas file. Therefore there is no great advantage to creating your own.
I do not plan to make use of the actual component shell itself as it offers no advantage that I can see over the imported type library.

 

How do I disable the [x] close button and the equivalent system menu item in Word?

Use the following code

var
  hWordWindow : HWND;
  hSysMenu    : HMENU;
begin
  // Find the Word 97 handle
  hWordWindow := FindWindow ('OpusApp', nil);
  if hWordWindow <> 0 then
  begin
    hSysMenu := GetSystemMenu (hWordWindow, false);
    EnableMenuItem (hSysMenu, SC_CLOSE, MF_BYCOMMAND or MF_GRAYED)
  end
end;

This info comes from a newgroup posting by Deborah Pate. I have now implemented this routine as a method of TWordApp (DisableSystemCloseBox). See redefining FileExit for more info on preventing the user from quitting Word.

BTW - does anyone know the origin of the rather peculiar 'OpusApp'  magic word?

 

I want to re-define what Word does for x menu item (e.g., FileNew, FileExit)

It is quite easy to get Word to perform a different task when the user chooses a standard menu item. Just use the standard action's name as the name of your macro, e.g.,

Sub FileNew
  ' This proc alters the "view" of the templates
  SendKeys "%2"
  Dialogs(wdDialogFileNew).Show
End Sub

If you cannot do what you want to do using VBA, you could always call a Delphi DLL. I am interested in ways to communicate back to the controlling Delphi application (rather than an in-process DLL). If anyone has done this or has a good idea how this could be done easily and reliably, I would be very interested to hear from you.
The demo program loads a file called Test.doc that contains a FileNew and FileExit macro. The FileExit macro traps the user trying to quit Word (either from the File.Exit menu or by pressing Alt-F4). Combined with TWordApp.DisableSystemCloseBox, this makes it rather difficult for the user to quit Word without your consent.

 

Delphi 5 comes with the Word objects as components - surely your classes are no longer needed?

Delphi 5 wraps the automation classes up into a set of 5 components (TWordApplication, TWordDocument, TWordFont, TWordLetterContent, TWordParagraphFormat). These components publish a standard set of OleServer properties and expose specific properties and methods as a public interface. The events are also specific to the object. The advantages of these new components over the existing classes are:

  1. They expose the component's events as easily as any other component - this has to be the main advantage
  2. They make it easy to connect to an existing server or create a new one
  3. They connect using early binding
  4. By using method overloading, they provide a way to make OLE default parameters easy to code

The components also have a number of down sides:

  1. There is no point that I can see to having any object other than the TWordApplication as a component as almost all users create the document object at runtime. I cannot see any use for the other three components. They seem to be have been created because the code generator found them
  2. By using method overloading, they have made the import libraries huge - they are now about 180% of what they were in Delphi 4. Although this is an attractive way to make optional parameters easy to code for, the alternative way (to actually use Delphi's optional parameters) is neater. However, you cannot make OleVariants optional (see next problem)
  3. They continue to use OleVariant for almost all parameters. This is a difficult to use type as you cannot substitute its value with a constant and you cannot give it a default value for optional parameters. My classes get round this by using standard Delphi types for the method parameters and converting them in code to OleVariants. This makes the code insight features more intuitive and makes optional parameters simpler to code.
  4. The events exposed by Word are inconsistent and incomplete and the classes do nothing to help with this. My classes go some way toward making the events useable.

 

[Fatal Error] AHWord97.pas(linexxx): File not found: 'Word_TLB.dcu' or Word97.pas'

I don't provide Word_TLB.pas for Delphi 4 & 5 two reasons:

1. It is huge - 859k when created by Delphi 4 and 699k when created by Delphi 3
2. It is Delphi version specific
3. You can easily roll-your-own:
    In Delphi choose File_Open
    Select Files of Type "Type Library" and open the file "C:\Program Files\Microsoft Office\Office\Msword8.olb"
    Make sure "$(DELPHI)\Imports" is in your Library path.
    If you own Delphi 3, you will need to make a small fix to the pascal file - see the
FAQ on "Application_"

NB I have now put the Delphi 3 version of Word_TLB.pas on my website due to frequent requests.
NB Delphi 5 imports the library as C:\Program Files\Borland\Delphi5\Ocx\Servers\Word97.pas
This is rather annoying and means I have had to change my unit to AHDelphi97.pas

 

[Error] AHWord97.pas(linexxx): Undeclared identifier: 'Application_'
[Error] AHWord97.pas(linexxx): Undeclared identifier: 'CoApplication_'

This occurs with Delphi 3 and is due to the way it creates the Word_TLB.pas file.

With COM objects, when you refer to an object, property or method, you are just refering to an GUID class ID (i.e., a big number). The actual name used is not important. You can change the names of entities in the type library pascal file and still use it (provided you refer to the new name in your code). This is similar to aliases for DLL imports. When Delphi imports a type library and writes out the pascal file, it alters quite a few of the objects names because of conflicts with its "reserved names". You will see this at the start of the file:

{ Conversion log:
  Warning: 'Object' is a reserved word. Parameter 'Object' in _Application.IsObjectValid changed to 'Object_'
  ...

Reserved words have been part of pascal from the beginning. They are fundamental names used in pascal that should not be used as identifiers to avoid confusion with the original pascal meaning. Since pascal is strictly typed, it won't allow you to reuse these special words (unlike C++). Delphi 3 does a fairly good job at removing these conflicts when it imports type libraries (Microsoft's type libraries are full of "reserved words"). However there is one object that was not a renamed because it was not a reserved word in Delphi 3. The object is of course "Application". Application is an object in the Forms.pas file. VCL object names were not considered important enough for reserving/renaming, even one as fundamental as Application - if you think you have never used it, you have not looked - you will find it in all your project files (*.dpr):

begin
  Application.Initialize;
  Application.CreateForm(TfrmDemo, frmDemo);
  Application.Run;
end.

Now, if you were to compile a program that "used" Word_TLB.pas, when the compiler gets to Application it may look in Word_TLB.pas first and complain at the lack of an Initialize method.The application object is also used elsewhere in the VCL and can be of use in your own code (e.g. to get the EXEname). You can specify which file to look in for an object by prefixing it with the unit name (e.g., Forms.Application or Word_TLB.Application). This is fine for your own code, but a hassle for the autocreated code in the dpr and you shouldn't change code in the VCL.

When Delphi 4 came along, Borland realised that this was a problem and made "Application" a reserved word in the eyes of the type library. Thus the Delphi 4 version of Word_TLB.pas uses "Application_" instead of "Application". NB do not confuse either with "_Application".

The solution to all this is to edit Word_TLB.pas. Now Delphi warns you that any edits to a type library pascal file will be overwritten if you re-import the type library. However, since the Word 97 type library will never change, you will never need to re-import it. The changes you should make are:

Line number Original line New line
3059 Application = _Application; Application_ = _Application;
many - do a search function Get_Application: Application; safecall; function Get_Application_: Application_; safecall;
many - do a search property Application: Application read Get_Application; property Application_: Application_ read Get_Application_;
many - do a search property Application: Application readonly dispid 1000; property Application_: Application_ readonly dispid 1000;
14663 CoApplication = class CoApplication_ = class
14709 class function CoApplication.Create: _Application; class function CoApplication_.Create: _Application;
14714 class function CoApplication.CreateRemote(const MachineName: string): _Application; class function CoApplication_.CreateRemote(const MachineName: string): _Application;

Note, the line numbers are for my copy of Word_TLB.pas produced by Delphi 3. They will probably be the same for you (but MS/Delphi service packs may alter this).

NB you can now get this amended file on my web site.
NB Delphi 5 changes these again and refers to WordApplication, WordDocument, etc..

 

Where is the Borland demo version of Word97?

If you installed Delphi in the default directory, you should find it at:

C:\Program Files\Borland\Delphi4\Demos\Activex\Oleauto\Word8\Word97.pas

or

C:\Program Files\Borland\Delphi5\Demos\Activex\Oleauto\Word8\AutoImpl.pas

 

Is there a C++ Builder version of these classes?

No. I don't own C++ Builder and have little C++ experience. I presume you could use the compiled unit in a version compatible with Delphi 4. I could send this unit to anyone who want to try it and doesn't have Delphi 4. I know there are some compatibility issued with sharing units but have no time/desire to find out. Jacques Mainville asked this question but the return e-mail address bounced -this is why you have not received a reply!

 

Which versions of Delphi can use these classes?

The classes were developed in Delphi 4. Quamar Ali has converted the original Word97 for Delphi 3. As new features have been added, I have tried to keep the Delphi 3 up-to-date. However, I no longer have Delphi 3 installed. All I can do is check that the code compiles in Delphi 4, using the Delphi 3 type library. Delphi 4 has lots of nice extras that make working with COM objects easier - especially default/optional parameters. Quamar Ali removed the event sink from the Delphi 3 version. I have no way of knowing if you can use event sinking in Delphi 3 - I suspect not. If someone wants to copy the code over and try - be my guest. I am assured by users that the Delphi 3 version does work.

Delphi 1 and 2 are not able to use these classes.

I have just made the classes Delphi 5 compatible.

 

Why use 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 (although Word is d*** slow so you may not notice - see Why is Word slow to load and eats vast amounts of memory?).
  2. It is language independent (more of a problem with Word versions before '97).
  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 installed on the same machine. 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).
  6. Late binding is for script interpreters - you bought Delphi because you're a grown-up and can handle the raw speed, why use crayons to write your masterpiece?

 

I'm odd - I would like to use late binding. How to I get a handle on Word?

Use the following code:

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

 

Delphi 3 has no EmptyParam, how can I survive?

To roll 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.

 

Why is Word slow to load and eats vast amounts of memory?

Short answer - because of the bloatware wars with WordPerfect.

Better answer -

- start Word (opens new document for you)
- in the new document, type the word Blue
- select this word and use Format->Font to change it to be bold and blue
- select OK
- enter a space after the word Blue
- choose Help->About
- on the Word icon, press Control-Shift-(Left)Click

After a bit of thinking, a full screen pinball game appears!

 

Automating Word is faster if I drop down a Word menu — How can I get this speed advantage in code?

This is a FAQ of the OleAutomation newsgroups. If you have a lot of automation to perform and you switch to Word, then click on the File menu, the automation process will be much faster. This is probably because Word suspends any background tasks (like spell-checking) when you do this. You may get a speed advantage if you turn off the intellisense, spelling and grammar features of Word (but preserve them when finished or you will upset the user). However, it appears that you still don't get the full advantage that you get when you drop a menu. If you have read this far in the hope that I have the answer — sorry! I have put this FAQ in so that anyone who finds the solution may feel charitable enough to share it with me (and the rest of the Delphi community). So far I have not seen a solution on the newsgroups.

 

I want to use Word to produce xxx, what is the best way to do this?

My AHWord97 classes were developed for writing one or two letters or a few pages of a report. Data is read from an Access file and heavily processed to generate natural English text for inserting inside Word. The processing is the main reason I use Delphi — I would have great difficulty coding it in VBA.

Having the letters in Word format is important so that the secretaries can save/edit them just like all their other letters.

When you are considering Word to generate letters consider the following:

  1. Are you sure  you want to use Word?
  • Word is huge, slow, bloated and bug-ridden. It takes a while to load up and automating it is not speedy.
  • You will need to supply your users with Word, or demand that they own it (costly).
  • Microsoft are not very good at backward compatibility. There is no guarrantee that code written to automate Word 97 will work for Word 2000 (actually, almost a dead-cert that it won't — does anyone know?). Word VBA code is likely to be upgradable though.
  • However templates are familiar to many users and allow easy customisation of letters.
  • If you have been disappointed with QuickReports, have a look at other report generators before dismissing them.
  1. Are you sure you want to use Delphi?

I know this is heresy, but there are good reasons for considering coding within Word VBA (see converting Word macros/VBA code to Delphi).
You can go half way and use my classes as well as call Word macros for part of the work.

You may also want to consider mail-merging if you are producing lots of similar letters.

Using Visual Basic itself (as opposed to Word VBA) has no advantages to my mind.

  1. Are you sure you want to write the letter/report by automating each step.

There are other methods to consider:

  1. Generate ASCII or RTF output and then import it into Word. You can do this by using Word's filters. You can also directly read/write the RTF of a Word document using OLE - see Joel Milne's Delphi / Word pages.
  2. Use QuickReport's Word filter. I have not studies this in depth, but I suspect this just exports RTF. I believe that little formatting or placement is kept during the transfer so it is probably of limited use. It is certainly not up to the quality of Access' Report to Word export.

 

How do I use Word inside the TOleContainer object?

There has been quite a lot written about TOleContainer in the OleAutomation newsgroup. Basically, it seems to be a partly implemented container class, unlike the Visual Basic version. It has been suggested that since the Borland team don't use it, it has not been developed to its full potential. I have no idea whether this is true — what is clear is that you are largely on your own here. I have read that some very brave/clever programmers have rewritten it to do what they need. However, these are in-house rewrites and I have yet to see TSuperOleContainer on Torry's.
Note that you should understand that the object inside TOleContainer is a single document, not the Word application. Therefor any menus/commands to do with opening/closing files will be invalid. You can get hold of the hidden application object from a document object (as I do in my classes) but there is less you can do with it. While it is neat to be able to put all of Word inside your application, I still feel it is more hassle than it is worth. It also makes the system much less flexible and you have to take over saving the documents. Users are now fairly familiar with having multiple applications open and switching between them as needed, why restrict them?

*** I have managed the impossible - I have got the TOleContainer to work and have converted the demo to show it off (Demo2.dpr). Nothing fancy as I don't use it - perhaps someone who does could add menu merging and thus answer that other FAQ - how do I redefine the menu File...

 

I want to convert my Word macros/VBA code to Delphi as Delphi is compiled and therefor fast

In general, don't.

Prototyping how to automate Word using macros is a great way to learn how to control Word from a Delphi program. However if you have existing macros in Word, particularly if they are big, keep them there. Just call the macro and let it run inside Word.

When Delphi automates Word it has to pass a fair amount across process boundaries. COM marshalls this so you don't have to think about it but you should be aware that this is a costly exercise, even when you use early binding. When Word executes VBA code, all the action happens inside Word's process space. Despite the fact that VBA is interpreted, automation commands run faster inside Word.

You may wish to convert to Delphi if you want to hide the code (although you can hide code in VBA). You will get a speed advantage if you have to do a fair amount of non-automation tasks (e.g, calculations/database lookups). Programming that kind of stuff is more enjoyable in Delphi too.

It is easier to amend VBA code inside a Word template that it is to recompile and re-install your program. Bear in mind that you can use the DAO and ADO within the VBA quite easily to read databases. If all you want to do is read names and addresses from a database and fill in the headings for a letter, this is almost certainly easier from inside Word that it is from Delphi.

If you were put off using Word basic and Word forms in the past, have a look at VBA for Word 97 now. I was pleasantly suprised at how easy it was to make up a simple form and put code behind it. Before I get shot as a traitor, I should say that the VBA environment and language is still miles behind Delphi. I can just about bear it for very simple forms (a bit like Access).

 

How do I pass parameters to Word macros?

The easiest method is to set up custom properties for the document in the template (or document). Set the property using Delphi and read the value in Word. See the demo for an example that calls a macro in the Word document with the "parameter" of today's date. The macro then generates a calendar for the month and highlights that day. Perhaps someone could translate this macro to Delphi and see if it is faster or slower? You can also use document variable too. The other big advantage of custom properties (and built in ones) is that you can read them using the MSoffice type library without loading the document into word. You can also use the file find tools in that library to hunt for documents with certain properties. This can make storing/sorting documents less of a headache.

 

Where can I get more information on Word automation and Delphi COM in general?


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