Word 97 Frequently Asked Questions
Last updated 22 August 1999
Delphi 5 comes with the Word objects as components - surely your classes are no longer needed?
[Fatal Error] Word97.pas(29): File not found: 'Word_TLB.dcu' or 'Word97.pas'
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 re-define what Word does for x menu item (e.g., FileNew)
I want to convert my Word macros/VBA code to Delphi as Delphi is compiled and therefor fast
Where can I get more information on Word automation and Delphi COM in general?
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:
Project Demo.exe raised exception class EOleSysError with message 'Class not registered'. Process stopped. Use Step or Run to continue.
class function CoGlobal.Create: _Global; |
begin |
Result :=
CreateComObject(CLASS_Global) as _Global; |
end; |
This is not a bug in the demo. The Global class is registered when any instance of Word is
present. If an exception occurs when trying to refer to the class, this implies that there
is no active Word present and the creator method will have to create a new one. While an
exception is raised when you run the code within Delphi, the exception will be silent in a
free-standing program.
I have updated the constructor to use a different means of getting the active application object. This uses GetActiveObject and checks its return value to see if it succeeds (most examples of using this function wrap it up in OleCheckall this procedure does is raise an appropriate exception if the function result indicates failure). I check for a specific error (no application object present) and deal with that by creating a new instance. Note that I still use OleCheck to cope with any other errors that I have not coded for. My demo should now run without any exceptions. Note that I now get the application object from "GetActiveObject" rather than from the Global object. In a senario where several instance of Word are in existance, this may not return the same instance of Word as before (haven't checked as it is of no relevance to me - may be important to you). Let me know if this is a problem.
[Fatal Error] Word97.pas(29): File not found: 'Word_TLB.dcu' or Word97.pas'
I don't provide Word_TLB.pas for 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 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] Word97.pas(267):
Undeclared identifier: 'Application_'
[Error] Word97.pas(271): 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 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.
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 Word97 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.
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.
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 Joel Milne's Delphi / Word pages.
- 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.
*** 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 re-define what Word does for x menu item (e.g., FileNew)
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.
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).
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.castle.net/~bly/Programming/Delphi/index.html
Deborah Pate's Delphi Automation page is http://www.djpate.freeserve.co.uk/Automation.htm