Word 97 Frequently Asked Questions
Last updated 7th December 1999
[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)
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?
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?
[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:
The components also have a number of down sides:
[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
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.
There are several reasons why early binding has not taken off with Word:
Here are a few reasons why early binding is better:
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:
- 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.
converting Word macros/VBA code to Delphi).I know this is heresy, but there are good reasons for considering coding within Word VBA (see
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.
Joel Milne's Delphi / Word pages.There are other methods to consider:
- 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
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.htmDeborah Pate's Delphi Automation page is http://www.djpate.freeserve.co.uk/Automation.htm