Saturday, April 30, 2011

Facts your mother never told you about Word automation

As I have probably written ad nauseam, I have written many programs for the Occupational Psychologist (OP) whom I support, and many modules within these programs write their output via Word and Excel. Ever since the OP bought new computers running Windows 7 and Office 2010, my programs have caused problems when outputting data to Word ('invalid pointer operation' is the usual error message. These problems don't occur on my computer, since I am working with Windows XP and Office 2003, nor on the old computers that the OP has.

I finally got motivated enough to hunt for the cause, and then the solution, to this problem. As usual in computer programming, finding the cause (aka debugging) took several hours whereas installing the fix took five minutes. The first step on the long road was changing the way that my programs would execute Word. I normally write this:
wrdApp:= CreateOleObject ('Word.Application');
wrdDoc:= wrdApp.Documents.Add ('q400.dot');
The first statement creates an instance of Word in the background, whereas the second statement creates a new document within Word, using the template called 'q400'. For the unsophisticated user, the first statement is equivalent to starting the Word program, and the second is equivalent to pressing 'New' and then choosing to create the new document based on the q400 template. The template is stored in Word's default template directory (DFD), so there is no need to specify the path to the template.

Incidentally, this DFD is normally inaccessible to ordinary users, which can be problematic at times. On the OP's computers, this directory is defined to be a specific directory which is accessible to all computers on her network, meaning that there is no need for multiple templates.

I started using what might possibly be a higher level of Word automation, writing code as follows:
WrdApp:= TWordApplication.Create(nil);
try
 WrdApp.ConnectKind:= ckNewInstance;
 WrdApp.Connect;
 Templ:= 'q400.dot';
 WrdDoc:= TWordDocument.Create (nil);
 try
   WrdDoc.ConnectTo
   (WrdApp.Documents.Add (Templ, emptyParam, emptyParam, emptyParam));
   wrdSel:= WrdApp.Selection;
 except
  on E: Exception do showmessage ('Error on WrdDoc.create ' + #13 + E.Message);
 end;
except
 on E: Exception do showmessage ('Error on WrdApp.create ' + #13 + E.Message);
end;
One annoying aspect of using TWordApplication and TWordDocument is that the arguments to their functions have to be of type 'OleVariant' and not simple integers or strings; this is why the 'templ' variable is assigned the value 'q400' and then passed as an argument to the 'add' procedure. My initial code didn't have the exception handling, but I'm pleased that I added it, as I could tell that there was an error whenever I tried to create the document. After scratching my head for a while, I realised that Word wasn't able to find the template that I wanted, even though it existed in the the default template directory.

It became clear after a while that I would have to pass the complete path to the template in order to open it. Obviously I couldn't write the naked path the source code, as the template's location on my computer is different to its location on the OP's computer. A quick Internet search revealed that one has to discover what the DFD is, then pass this value to the 'open' function. Fortunately, there is a way to do this, although it can only be done after the Word instance has been created (as opposed to getting the value from the registry, for example).
dir:= wrdApp.Options.DefaultFilePath[wdUserTemplatesPath];
Templ:= dir + '\q400.dot';
Once I had added this code, I no longer got the dreaded 'Invalid pointer operation' message! Backtracking, I realised that I didn't need to use the TWordApplication/TWordDocument type code and could continue to use my previous 'CreateOleObject' code, as long as the template's location was defined explicitly.

This is one definite change between the automation code needed for Office 2010 and for previous versions. It's a shame that I found no reference to it anywhere (most automation code tends not to use templates).

No comments: