Problem with Word and Document FormFields

J

Jon Robertson

We use Word automation to generate new Word documents using information from
our database. We start Word, load a template that has form fields, iterate
through the form fields and replace each one with a value from the database.

Note that particular fields may represent a graphic or a table. In this
case, we insert the graphic or table at the anchor of the field and then
delete the field. For all other fields, we simply replace the Result
property of the field with the value from the database.

Below is a snip of code, although it's Delphi. I haven't tried to reproduce
this from VB or VBA. Much error/exception handling has been removed from the
snip to make the code easier to read here.

The problem is that FormFields.Item(I) will frequently return "Server threw
an exception". It happens on different templates, each having a different
number of form fields and a different list of form fields from our software.
Yet the exception always occurs on field 10 of the collection.

To repeat, the exception ONLY occurs when I = 10.

We've seen this happen with Word 2000, 2002 (XP), and 2003. Any suggestions
would be greatly appreciated.

Code snip:

procedure TWordDoc.FillTemplateFields(FormFields: OleVariant);
var
I : Integer;
LinkToFile : OleVariant;
SaveWithDocument : OleVariant;
Anchor : OleVariant;
MyField : OleVariant;
cdsDocFields : TClientDataSet;
FieldList : TStringList;
Sig : OleVariant;

begin
I := 1;
iFieldCount := -1;
while I <= FormFields.Count do begin

MyField := FormFields.Item(I);

if MyField.Result = 'DR_Signature' then begin
LinkToFile := False;
SaveWithDocument := True;
Anchor := MyField.Range;
Sig := FWordDoc.InlineShapes.AddPicture(FSigFile, LinkToFile,
SaveWithDocument, Anchor);
Sig.Height := 0.75 * 72.0;
Sig.Width := 3.00 * 72.0;

MyField.Delete;
Continue;
end //if dr_signature

MyField.Range.Fields.Item(1).Result.Text :=
cdsDocFields.FieldByName(FieldList[J]).DisplayText;
Inc(I); // Since MyField was not deleted, increment I to the next field.
end;
end;
 
C

Cindy M -WordMVP-

Hi Jon,

I'll start by saying that the behavior you report is completely new to me; I
have no idea where it's coming from. However, it would never, ever have occurred
to me to use this approach. Can you explain WHY you're using formfields as the
targets?

On a more general note: when you loop through a collection of objects in an
Office application, and start deleting any of them as part of the process,
you'll often run into problems. Deleting something from the collection has a
nasty tendency to change the Items indexing. The most common problem one runs
into is that only every second item is processed. Possibly, this is what you're
running into.

When we (Office developers) loop a collection with the intention of changing the
number of members in the collection, we start the loop from the end and work
towards the top. In VBA-speak: For i = coll.Count to 1 Step -1

Try that and see if it makes any difference in what you're seeing?
We use Word automation to generate new Word documents using information from
our database. We start Word, load a template that has form fields, iterate
through the form fields and replace each one with a value from the database.

Note that particular fields may represent a graphic or a table. In this
case, we insert the graphic or table at the anchor of the field and then
delete the field. For all other fields, we simply replace the Result
property of the field with the value from the database.

Below is a snip of code, although it's Delphi. I haven't tried to reproduce
this from VB or VBA. Much error/exception handling has been removed from the
snip to make the code easier to read here.

The problem is that FormFields.Item(I) will frequently return "Server threw
an exception". It happens on different templates, each having a different
number of form fields and a different list of form fields from our software.
Yet the exception always occurs on field 10 of the collection.

To repeat, the exception ONLY occurs when I = 10.

We've seen this happen with Word 2000, 2002 (XP), and 2003. Any suggestions
would be greatly appreciated.

Code snip:

procedure TWordDoc.FillTemplateFields(FormFields: OleVariant);
var
I : Integer;
LinkToFile : OleVariant;
SaveWithDocument : OleVariant;
Anchor : OleVariant;
MyField : OleVariant;
cdsDocFields : TClientDataSet;
FieldList : TStringList;
Sig : OleVariant;

begin
I := 1;
iFieldCount := -1;
while I <= FormFields.Count do begin

MyField := FormFields.Item(I);

if MyField.Result = 'DR_Signature' then begin
LinkToFile := False;
SaveWithDocument := True;
Anchor := MyField.Range;
Sig := FWordDoc.InlineShapes.AddPicture(FSigFile, LinkToFile,
SaveWithDocument, Anchor);
Sig.Height := 0.75 * 72.0;
Sig.Width := 3.00 * 72.0;

MyField.Delete;
Continue;
end //if dr_signature

MyField.Range.Fields.Item(1).Result.Text :=
cdsDocFields.FieldByName(FieldList[J]).DisplayText;
Inc(I); // Since MyField was not deleted, increment I to the next field.
end;
end;

Cindy Meister
INTER-Solutions, Switzerland
http://homepage.swissonline.ch/cindymeister (last update Jun 8 2004)
http://www.word.mvps.org

This reply is posted in the Newsgroup; please post any follow question or reply
in the newsgroup and not by e-mail :)
 
J

Jon Robertson

However, it would never, ever have occurred to me to use this approach.
Can you explain WHY you're using formfields as the targets?

Funny, it never occurred to us to use anything else. Form fields, by
design, hold data that may change with each document. Whether it's a system
category field, such as Date & Time, Mail Merge, Formula, etc; or a user
field, where the user types a value each time.

For example, if we wanted a template where the user typed the customer's
name each time, we'd likely use a form field. Therefore, it made sense to us
that if we automated the population of the template, we'd still use a form
field.

How would you have done it?
On a more general note: when you loop through a collection of objects in an
Office application, and start deleting any of them as part of the process,
you'll often run into problems.

We weren't originally deleting the fields. We started with this version
because we are now inserting data that may not fit in a form field, such as a
picture or a table. The "unused" form fields were "messy" on the generated
document. However, walking through the document's form fields after each
deletion, in a separate loop, indicated that the code was working as
intended. Nevertheless, it is one of the items that was changed and I'll
focus my efforts there.
Deleting something from the collection has a nasty tendency to change the
Items indexing.

I had not seen this during testing. Good to know.
When we (Office developers) loop a collection with the intention of changing the
number of members in the collection, we start the loop from the end and work
towards the top. In VBA-speak: For i = coll.Count to 1 Step -1

Try that and see if it makes any difference in what you're seeing?

I will. Thank you very much for your post Cindy. It was very informative
and helpful.

Instead of deleting the field, is there a way to make the field hidden? I
tried setting the range, as below, but that didn't work:
FormFields(I).End = FormFields(I).Start

Thanks again.

Jon
 
J

Jon Robertson

Cindy said:
when you loop through a collection of objects in an
Office application, and start deleting any of them as
part of the process, you'll often run into problems.
Deleting something from the collection has a nasty tendency
to change the Items indexing. The most common problem one
runs into is that only every second item is processed.

This is easily explained if the looping mechanism is a for loop, or
something similar, that always increments the loop index after each
iteration. The issue is that if the user deletes the current item,
then the index of the next item is changed to the index of the current
item. But at the end of the loop, the looping mechanism increments the
loop index, thereby skipping the "next item". Take the example I used
in another post:

FieldOne (Index 1)
FieldTwo (Index 2)
FieldThree (Index 3)

Now, use the following (untested) code. Note that I'm assuming that VB
will check FormFields.Count after each iteration. I don't use VB
enough to be confident of this fact.

For I = 1 To FormFields.Count
MsgBox(FormFields.Item(I).Name)

If FormFields.Item(I).Name = "FieldOne"
FormFields.Item(I).Delete
End If
Next

You'll never see a MsgBox for "FieldTwo". The loop will skip from
"FieldOne" to "FieldThree", even though "FieldTwo" was not deleted.
Here's what happens:

The first iteration, I = 1 and FormFields.Items(I) is FieldOne. The
MsgBox occurs, then FieldOne is deleted. At this point, the index of
FieldTwo becomes 1, and the index of FieldThree becomes 2.

The second iteration, I = 2 because the For incremented it.
FormFields.Items(I) is FieldThree. The MsgBox shows "FieldThree" and
FieldTwo is "skipped" by the loop.

When you delete an item of the collection in a loop, you must not
increment the loop counter, otherwise you'll skip the next item. This
will lead to the behavior "only every second item is processed." This
is not a bug, this is improper looping in a scenario where some items
may be deleted (or inserted).

Instead, use a looping mechanism that requires the programmer to
increment the loop index, such as a While loop, and only increment the
index if the current item was NOT deleted. Such as:

I = 1
While I <= FormFields.Count
MsgBox(FormFields.Item(I).Name)

If FormFields.Item(I).Name = "FieldOne"
FormFields.Item(I).Delete
Else
I = I + 1
End If
Wend

This loop will work correctly, without "skipping" items. I hope this
helps explain the behavior that you referred to.
 
C

Cindy M -WordMVP-

Hi =?Utf-8?B?Sm9uIFJvYmVydHNvbg==?=,
Funny, it never occurred to us to use anything else. Form fields, by
design, hold data that may change with each document. Whether it's a system
category field, such as Date & Time, Mail Merge, Formula, etc; or a user
field, where the user types a value each time.
Form fields were primarily designed for user-input, and to be used in a
protected document. If you want to "dump in" just text, form fields are fine.
As you noticed, they really aren't designed to take anything BUT plain text.

Traditionally, Word people will use BOOKMARKS as data targets. You can put
anything into a bookmark. A form field name is actually a bookmark, BTW. One
reason to NOT use bookmarks is that they're too easily deleted by the user, if
the document is one the user will edit, and you still need the bookmark targets
later.

Fields are finicky things; if you're not careful when you're automating you can
end up putting things into the "hidden parts" of the fields (I call it stuffing
the field brackets). That can have all kinds of nasty results. If your problem
weren't so regularly reproducible, this is where I'd start looking.

In any case, try: declare a Range object variable (Anchor), set it to the
target (field) range, delete the field and THEN insert the object into the
range (the range should still exist).

Cindy Meister
INTER-Solutions, Switzerland
http://homepage.swissonline.ch/cindymeister (last update Jun 8 2004)
http://www.word.mvps.org

This reply is posted in the Newsgroup; please post any follow question or reply
in the newsgroup and not by e-mail :)
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top