Header / Footer mail merge issue with custom built word automation solution.

E

eJimmi

Office professional 2003
Visual studio 2005
C#
Example mail merge field from header: «Field:Name»

I have smart client that use web services to provide data for mail
merge. The templates contain Custom tokens inserted in the document as
mail merge fields eg {{Field:StudentNumber}}, {{Table:CourseUnits}}.
The client parses the tokens and populates document with the relevant
data. All works well until these fields are in the header or footer.

The main method for performing the merge loops through the supplied
data rows and calls the ProcessFields method passing in the fields
collection from the document, header and footer.


Code stub from the execute method
DataRowCollection rows = _Data.Tables[0].Rows;
for (int i=0; i < rows.Count; i++)
{
Word.Document document = null;
if (_DocumentType == DocumentType.Template)
{
document = _Assistant.AddDocument(_FilePath, true);
}
else
{
document = _Assistant.OpenDocument(_FilePath, true);
}
if (_BeforeRowProcessed != null)
{
_BeforeRowProcessed(this, document, rows, i);
}
// document.MailMerge.Destination = _Destination;
Word.HeaderFooter header =
document.Sections[1].Headers[Word.WdHeaderFooterIndex.wdHeaderFooterPrimary];
if (header.Exists)
{
ProcessFields(header.Range.Fields, rows);
}
ProcessFields(document.Fields, rows);
Word.HeaderFooter footer =
document.Sections[1].Footers[Word.WdHeaderFooterIndex.wdHeaderFooterPrimary];
if (footer.Exists)
{
ProcessFields(footer.Range.Fields, rows);
}
if (_AfterRowProcessed != null)
{
_AfterRowProcessed(this, document, rows, i);
}
}


private void ProcessFields(Word.Fields fields, DataRow row)
{
foreach (Word.Field field in fields)
{
field.Select();
Word.Range range = _Assistant.Application.Selection.Range;
if (_OnFieldSelect != null)
{
_OnFieldSelect(this, field, row);
}
ResolveToken(field, row);
}
}


The resolve ResolveToken method maps the word merge field to the
database field writing the text with:

field.Result.Text = DatabaseAssistant.SafeStringValue(row[tokenKey]);

The best I have mamanged is to have either of the header or footer edit
pane open with the resolved value however when I close the window the
merge field only is displayed - the value is gone. I cannot see the
value in the print preview either. Initially I'm only concerned with
the primary header or footer - old tackle the variations of headers /
footers at a later stage.

This is the first Word automation I've built so any pointers will be
appreciated.

JS
 
C

Cindy M -WordMVP-

Hi Jimmi,

Hmmm. "Mail merge" is a very specific thing in Word terminology. Given that
you're working with a web service, you can't be doing a true mail merge, so you
shouldn't use that term when discussing your project - it confuses people like
us :)

OK, so you're passing data into "targets" in a Word document, and you've chosen
to use Mergefields (I guess, it's hard to be sure if that's what you really
have) as the targets. This works in the body of the document, but not in the
header/footer.

The reason is fairly clear: you're trying to use the Selection object. Fact is,
physically selecting a header/footer via automation in a Word document just
doesn't work very well. Actually, you should avoid *selecting* anything when
automating Word, unless there's no other way to accomplish the task.

It's much better to work exclusively (if possible) with the RANGE object. You
do this up the point of passing control to the ProcessFields method. A field
obejct doesn't have a Range property, but it does have a Result property which
returns a Range. You do not have to select the field in order to access the
Result property.

Next problem: Word fields are actually dynamic entities - they aren't meant to
serve as "data targets". With one single exception (form fields), if you assign
something to Field.Result.Text that information will be lost as soon as the
field receives a command to update (such as pressing F9 when the field is
selected). This is how Word is designed to work.

If the data you're passing in should be permanent, then you should work more
like this:
Word.Field fld = footer.Fields;
Word.Range rng = fld.Result;
fld.Delete;
rng.Text = "my data";
Office professional 2003
Visual studio 2005
C#
Example mail merge field from header: «Field:Name»

I have smart client that use web services to provide data for mail
merge. The templates contain Custom tokens inserted in the document as
mail merge fields eg {{Field:StudentNumber}}, {{Table:CourseUnits}}.
The client parses the tokens and populates document with the relevant
data. All works well until these fields are in the header or footer.

The main method for performing the merge loops through the supplied
data rows and calls the ProcessFields method passing in the fields
collection from the document, header and footer.


Code stub from the execute method
DataRowCollection rows = Data.Tables[0].Rows;
for (int i=0; i < rows.Count; i++)
{
Word.Document document = null;
if ( DocumentType == DocumentType.Template)
{
document = Assistant.AddDocument( FilePath, true);
}
else
{
document = Assistant.OpenDocument( FilePath, true);
}
if ( BeforeRowProcessed != null)
{
BeforeRowProcessed(this, document, rows, i);
}
// document.MailMerge.Destination = Destination;
Word.HeaderFooter header =
document.Sections[1].Headers[Word.WdHeaderFooterIndex.wdHeaderFooterPrimary];
if (header.Exists)
{
ProcessFields(header.Range.Fields, rows);
}
ProcessFields(document.Fields, rows);
Word.HeaderFooter footer =
document.Sections[1].Footers[Word.WdHeaderFooterIndex.wdHeaderFooterPrimary];
if (footer.Exists)
{
ProcessFields(footer.Range.Fields, rows);
}
if ( AfterRowProcessed != null)
{
AfterRowProcessed(this, document, rows, i);
}
}


private void ProcessFields(Word.Fields fields, DataRow row)
{
foreach (Word.Field field in fields)
{
field.Select();
Word.Range range = Assistant.Application.Selection.Range;
if ( OnFieldSelect != null)
{
OnFieldSelect(this, field, row);
}
ResolveToken(field, row);
}
}


The resolve ResolveToken method maps the word merge field to the
database field writing the text with:

field.Result.Text = DatabaseAssistant.SafeStringValue(row[tokenKey]);

The best I have mamanged is to have either of the header or footer edit
pane open with the resolved value however when I close the window the
merge field only is displayed - the value is gone. I cannot see the
value in the print preview either. Initially I'm only concerned with
the primary header or footer - old tackle the variations of headers /
footers at a later stage.

This is the first Word automation I've built so any pointers will be
appreciated.


Cindy Meister
INTER-Solutions, Switzerland
http://homepage.swissonline.ch/cindymeister (last update Jun 17 2005)
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 :)
 
E

eJimmi

Hi Cindy

Thank you so much for your advice. I could not have built a solution
without your website and contributions to the newsgroups from yourself
and the other contributing MVP's.

Re "word terminology" and "Mail Merge" - I agree, and have
adopted the "targets" term in my documentation. You are correct in
your deductions - and I am using the fields as data targets and the
data I'm passing in is to be permanent. I have implemented your
suggested code block and it works beautifully.

As I'm not performing a mail merge (there is no
document.MailMerge.DataSource) I'm assuming I will need to implement my
own custom code to emulate the
WdMailMergeDestination.wdSendToNewDocument..wdSendToPrinter
functionality? I'm pretty sure I already know the answer.

Many thanks again

Jaimie Sims (e-Jimmi).
 
C

Cindy M -WordMVP-

Hi Jaimie,
As I'm not performing a mail merge (there is no
document.MailMerge.DataSource) I'm assuming I will need to implement my
own custom code to emulate the
WdMailMergeDestination.wdSendToNewDocument..wdSendToPrinter
functionality? I'm pretty sure I already know the answer.
<LOL>Yes, in that case you do have to code it all. Question then becomes,
of course, what's the most efficient approach.

One possibility would be to close the document and start over again with
a new copy. But that would be comparatively slow.

I might be more inclined to begin by setting a BOOKMARK around each
mergefield target (do that in your loop, rather than writing in the
text).

Then loop. In the loop, write the text to Bookmark.Range.Text (which
should remove the mergefield on the first pass), and then recreate the
bookmark so that it will always be there. Very roughly, in VBA-speak:
Dim rng as Word.Range
Dim bkmName as String

Set rng = document.Bookmarks(bkmName).Range
rng.Text = "the data"
document.Bookmarks.Add Name:=bkmName, Range:=rng

Cindy Meister
INTER-Solutions, Switzerland
http://homepage.swissonline.ch/cindymeister (last update Jun 17 2005)
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 :)
 
E

eJimmi

Hi Cindy

Thanks again for going the extra yards. I'll look at implmenting this
in the coming weeks (other bigger fires to deal with now).

take care
JS
 

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