Art  Delphi  Automation  History  Home  Politics  Email me Default Colours  Printable Colours

Automating Microsoft Word

Contents


Sources of information

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
Cindy Meister's Word FAQ
Jonathan West's WordFAQ
MS Visual Basic Programmer's Guide
Microsoft Developer's Network

Books Charlie Calvert's Delphi 4 Unleashed

Back to top

How do I ... ?

Code examples are given for Word 97+ first, but Word Basic examples are given in boxes for those using earlier versions of Word.

Back to top

>>>>>How to start 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.

Back to 'HowDoI..?'

>>>>>How to close Word<<<<<

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.

 

Back to 'HowDoI..?'

>>>>>How to create a new document<<<<<

In Word 97:
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.


Back to 'HowDoI..?'

>>>>>How to open an existing document<<<<<

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.

Back to 'HowDoI..?'

>>>>>How to close a document<<<<<

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.


Back to 'HowDoI..?'

>>>>>How to insert text<<<<<

  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');

Back to 'HowDoI..?'

>>>>>How to format 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.


Back to 'HowDoI..?'

>>>>>How to create and access tables<<<<<

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 points
You 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);

Back to 'HowDoI..?'

>>>>>How to get document properties<<<<<

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. :)


Back to 'HowDoI..?'

>>>>>How to get the current position of the cursor<<<<<

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.

Back to 'HowDoI..?'

Sample projects to download

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's WordContainer component

Brian Lowe has written a component, descending from TOleContainer, that encapsulates a lot of Word functionality. Download it here!