Art Delphi Automation History Home Politics Email me Default Colours Printable Colours
| Web sites | Delphi sites Allan Harkness's Word automation page Borland's papers Chapter 17 of C. Calvert's D4 Unleashed Graham Marshall's Delphi 3 and Word Joel Milne's Word FAQ For catching Word events, or general COM concepts, see also Binh Ly's tutorials Non-Delphi sites |
| Books | Charlie Calvert's Delphi 4 Unleashed |
Code examples are given for Word 97+ first, but Word Basic examples are given in boxes for those using earlier versions of Word.
Using the D5 components to start Word
Starting up Word with the new D5 components is a piece of cake. Here's an example:
WordApplication1.Connect; WordApplication1.Visible := True;
After you've connected, you can use the ConnectTo method of the other Word components to associate them with other Word elements, such as a Word document:
WordDocument1.ConnectTo(WordApplication1.ActiveDocument);
Using the type library (early binding)
Before you can use this method, you must have imported the type library (MSWord8.olb for Word 97).
One way of starting Word is to try the GetActiveObject call, to get a running instance of Word, but put a call to CoApplication.Create in an except clause. But except clauses are slow, and can cause problems within the IDE for people who like Break On Exceptions set to True. The following code removes the need for a try...except clause, by avoiding using OleCheck on GetActiveObject in the case when Word is not running.
uses ComObj, ActiveX, Word_TLB; // or Word97; or Word2000; for D5 users var Word: _Application; AppWasRunning: boolean; // tells you if you can close Word when you've finished Unknown: IUnknown; Result: HResult; begin AppWasRunning := False; {$IFDEF VER120} // Delphi 4 Result := GetActiveObject(CLASS_Application_, nil, Unknown); if (Result = MK_E_UNAVAILABLE) then Word := CoApplication_.Create {$ELSE} // Delphi 5 Result := GetActiveObject(CLASS_WordApplication, nil, Unknown); if (Result = MK_E_UNAVAILABLE) then Word := CoWordApplication.Create {$ENDIF} else begin { make sure no other error occurred during GetActiveObject } OleCheck(Result); OleCheck(Unknown.QueryInterface(_Application, Word)); AppWasRunning := True; end; Word.Visible := True; ...Thanks to Allan Harkness for this tip. He has written a comprehensive TWordApp class that uses this method, which you can download from his site.
Without using the type library
Automation is so much easier and faster using type libraries (early binding) that you should avoid managing without if at all possible. But if you really can't, here's how to get started:
var
Word: Variant;
begin
try
Word := GetActiveOleObject('Word.Application');
except
Word := CreateOleObject('Word.Application');
end;
Word.Visible := True;
Or, with earlier versions of Word:
var
Word: Variant;
begin
try
Word := GetActiveOleObject('Word.Basic');
except
Word := CreateOleObject('Word.Basic');
end;
Word.Visible := True;
By using GetActiveOleObject, you use an instance of Word that's already running, if there is one.
Here's the quick version:
var SaveChanges: OleVariant; begin SaveChanges := wdDoNotSaveChanges; Word.Quit(SaveChanges, EmptyParam, EmptyParam);
Other possible values for the SaveChanges parameter are wdSaveChanges and wdPromptToSaveChanges - pretty self-explanatory, but if you're using late binding you'll need to define them in your own code like this:
const
wdDoNotSaveChanges = $00000000;
wdSaveChanges = $FFFFFFFF;
wdPromptToSaveChanges = $FFFFFFFE;
The second parameter is used for documents not in Word format. The possible values are wdOriginalDocumentFormat, or wdPromptUser, or wdWordDocument. Again, in late binding, you can declare these yourself:
const
wdWordDocument = $00000000;
wdOriginalDocumentFormat = $00000001;
wdPromptUser = $00000002;
The last parameter should be set to True if you want the document to be routed to the next recipient in line.
| Word Basic command |
Word.FileExit(1); quits Word, saving any modified file. If you pass 2 as the parameter, files are not saved; if the parameter is 0 or omitted, the user is prompted. |
Word.Documents.Add(EmptyParam, EmptyParam);
If you want the new document to be based on a template other than the Normal template, pass the name (and path) of the template as the first parameter. If you want to open the new document as a template, pass True for the second parameter.
In Word 2000, the Documents.Add method has two extra parameters, for the document type, and for specifying whether the document should be visible on screen. But using this method on a Word 97 machine will cause an exception, so if you need your code to be compatible with Word 97, use Word 2000's Documents.AddOld method. This takes the same parameters as the Word 97 Add method.
| Word Basic command - optional parameters
in square brackets [] |
Word.FileNew[Template = text] [, NewTemplate = number]; If you want the new document to be based on a template other than the Normal template, pass the name (and path) of the template as the first parameter. If you want to open the new document as a template, pass 1 for the second parameter. |
In word 97:
var FileName: OleVariant; begin FileName := 'C:\My Documents\The file I want to open.doc'; Word.Documents.Open(FileName, EmptyParam, EmptyParam, EmptyParam, EmptyParam, EmptyParam, EmptyParam, EmptyParam, EmptyParam, EmptyParam);
The optional parameters here include ReadOnly (the third parameter, default False), PasswordDocument (the fifth parameter, pass the password string for the document), and Format (the last parameter, lets you specify the file converter to be used).
In Word 2000, the Documents.Open method has two extra parameters, for encoding, and for specifying whether the document should be visible on screen. But using this method on a Word 97 machine will cause an exception, so if you need your code to be compatible with Word 97, use Word 2000's Documents.OpenOld method. This takes the same parameters as the Word 97 Open method.
| Word Basic command |
Word.FileOpen{'C:\Docs\The file I want to open.doc'); Look in the WordBasic help file for information on the (numerous) optional parameters for FileOpen. |
var SaveChs: olevariant; begin SaveChs := wdSaveChanges; Word.ActiveDocument.Close(SaveChs, EmptyParam, EmptyParam);
The parameters are the same as for the Word.Close method, but with one unfortunate exception: there's a bug which stops the wdPromptToSaveChanges value working in this method. So if you want the user to be asked whether to save changes, use the ActiveWindow.Close method instead (see Example).
| Word Basic command |
Word.FileClose(1); To save the file, pass 1 as the first parameter; to close without saving, pass 2; to ask the user whether to save it first you can pass 0, or omit the parameter altogether. |
var
S: Selection;
...
S :=Word.Selection;
S.TypeText('Here is some text');
S.TypeParagraph;
S.TypeParagraph;
S.TypeText('And there was a blank line.');
If the Application.Options.ReplaceSelection property is True, any text selected will be overwritten by the new text.
| Word Basic command |
Word.Insert('Here is some text'); |
var
S: Selection;
...
S := Word.Selection;
{Write the next sentence in bold type}
S.Font.Bold := integer(True);
S.TypeText('Be bold!');
S.Font.Bold := integer(False);
S.TypeParagraph;
{Write the next sentence in italic type}
S.Font.Italic := integer(True);
S.TypeText('Be daring!');
S.Font.Italic := integer(False);
When using the selection object, remember that if the Application.Options.ReplaceSelection property is True, any text selected will be overwritten by the new text.
You can create tables like this (tested on Word97):
var Doc: _Document; T: Table; begin Doc := Word.ActiveDocument; T := Doc.Tables.Add(Word.Selection.Range, 5, 3); T.Cell(1, 1).Range.Text := 'January'; T.Cell(1, 2).Range.Text := 'February'; T.Cell(1, 3).Range.Text := 'March'; T.Columns.Width := 72; // in pointsYou can read the data in the same way:
Caption := T.Cell(1, 3).Range.Text;
But using tables is very slow in Word (even worse with Word 2000). If you can
you should put the text in first and then convert it to a table at the last possible
moment, like this for example:
const
Line1 = 'January,February,March';
Line2 = '31,28,31';
Line3 = '31,59,90';
var
R: Range;
Direction, Separator, Format: OleVariant;
begin
Doc := Word.ActiveDocument;
R := Word.Selection.Range;
Direction := wdCollapseEnd;
R.Collapse(Direction);
R.InsertAfter(Line1);
R.InsertParagraphAfter;
R.InsertAfter(Line2);
R.InsertParagraphAfter;
R.InsertAfter(Line3);
R.InsertParagraphAfter;
Separator := ',';
Format := wdTableFormatGrid1;
R.ConvertToTable(Separator, EmptyParam, EmptyParam,
EmptyParam, Format, EmptyParam,
EmptyParam, EmptyParam, EmptyParam,
EmptyParam, EmptyParam, EmptyParam,
EmptyParam, EmptyParam);
Use variants for this. (So many of the DocumentProperties calls return IDispatch interfaces, rather than the specific interface you want, that trying to do this through early binding is far too complicated to be worth the effort.)
uses Word97; // or Word2000, or Word_TLB for Delphi 4 var Doc: OleVariant; ... Doc := Word.ActiveDocument; Caption := Doc.BuiltInDocumentProperties['Template'].Value; ShowMessage(Doc.BuiltInDocumentProperties[wdPropertyTitle].Value); ShowMessage(Doc.BuiltInDocumentProperties[wdPropertyAuthor].Value);
As you see, properties can be accessed either by name or by one of the wdProperty constants. You can also loop through the BuiltInDocumentProperties collection - but don't do this unless you really have to, because it will cause the word/page count of the document to be recalculated. If the document is of any length, users won't thank you for this. :)
The Selection object's information property can tell you this. Here's an example:
VertPos := Word.Selection.Information[wdVerticalPositionRelativeToPage]; HorizPos := Word.Selection.Information[wdHorizontalPositionRelativeToPage];
This code will tell you the vertical position of the selection - which is the cursor (insertion point) if no text is selected. The answer will be in twips, or 1/1440ths of an inch. To get the line number the cursor is on, you'd use
Word.Selection.Information[wdFirstCharacterLineNumber];
instead. Look up the Information property in the VBAWrd help to see all the other things you can find out this way - it's quite a long list.
Using the D5 components - a sample showing basic text entry, formatting, and searching
Using Word's mailmerge facility - uses Word's built-in mailmerge to create letters for every company in the 'customer.db' table that comes with Delphi. You must have Delphi 4 (any version) or D5 (Pro version or better) to run this, with the demos installed..
Using bookmarks and properties - a very simple example of using bookmarks and custom document properties to create a report. You must have Delphi 4 (any version) or D5 (Pro version or better) to run this, with the demos installed.
Example project by Wayne Herbert - many thanks to Wayne for this larger example project. Please note you must keep the watermark files and the data file in the same directory as the executable.
Brian Lowe has written a component, descending from TOleContainer, that encapsulates a lot of Word functionality. Download it here!