My C# VSTO Add-In for Word helps generate specialized labels for a Mail Merge. Mailing Labels are created in the document, and the first label in the "table" of labels is populated programmatically:
Word.Field myField = myCell.Range.Fields.Add(range, missing, missing, missing);
myField.Code.Text = " MERGEFIELD MyField ";
Now, when Word creates the mailing labels for a mail merge, it puts field codes where the next record goes: they look like { NEXT }
or «Next Record»
in Word.
In Word's "Mailings" tab, there's a built-in button called Update Labels. Its Office Control ID is MailMergeUpdateLabels
. If you have a table of Mailing Labels open, it will duplicate everything in the first label into every single valid label space. It also ensures that the «Next Record»
fields begin every label after the first, so that the mail merge can insert the next record.
Before Update Labels / After Update Labels
I need to trigger this action programmatically through C#. It would be very cheeky if I were to tell the user "please go to the mailings tab and press the Update Labels button". I can think of three ways that this could be solved:
Trigger the same method that the Update Labels button uses through Word Interop.
myObject.Range.Fields.Update()
does not accomplish this.).Reference the button and simulate a click.
updateButton.PerformClick();
, but I have no idea how to expose the built-in button through Interop so that I could reference it in my code.Write a method that achieves the same result. After all, I might be chasing the XY Problem.
For reference, here is the method that I use to populate a single label cell:
private void InsertIntoCell(Word.Cell cell, bool insertNextField)
{
string[] fieldCodes = { @" NEXT ",
@" MERGEFIELD Name ",
@" MERGEFIELD SerialNumber \b "": "" ",
"\v",
@" MERGEBARCODE Barcode CODE128 " };
cell.Range.Delete();
Word.Range range = cell.Range;
range.Collapse(WdCollapseDirection.wdCollapseStart);
foreach(string fieldCode in fieldCodes)
{
//the NEXT field is the first one in the fieldcodes array
//skip it if the program asks not to insert it
if (!insertNextField)
{
insertNextField = true;
continue;
}
//if the field codes demand a Vertical Tab character
//write it, because word reads it as a newline character
if(fieldCode.Equals("\v"))
{
range.Text = fieldCode;
range.Move(WdUnits.wdCharacter, 1);
continue;
}
Word.Field field = cell.Range.Fields.Add(range, missing, missing, missing);
field.Code.Text = fieldCode;
//move the range to the end of the most recent field
range = field.Result;
range.Collapse(WdCollapseDirection.wdCollapseEnd);
}
//set font, update the fields to make them visible
cell.Range.Font.Size = FIELD_FONT_SIZE;
cell.Range.Fields.Update();
}
Again, this way is very slow. I could use Selections and programmatically copy-paste (yes, using the user's clipboard), but that seems dirty. Even then, Update Labels is still superior, because it "knows" where to put «Next Record»
if it is deleted by the user.
I am mostly curious about how Option 2 would be done. If I am shown how to expose the Update Labels button to clicks, I could learn to expose any button to simulated clicks.
Yes, it's possible to do both Option 1 as well as Option 2. Both approaches are a bit obscure, so it's not surprising you didn't come across them.
Option 1: For some odd reason the command was not included in the standard object model. But it is accessible through the late bound WordBasic
object model. (Actually, all commands in Word have a corresponding WordBasic
implementation since behind the scenes that still runs everything. But they're not documented...)
The VB languages can use this directly. C#, however, can't work directly with late-bound objects so you need PInvoke:
object wordBasic = wdApp.GetType().InvokeMember("WordBasic", BindingFlags.GetProperty, null, wdApp, null);
wordBasic.GetType().InvokeMember("MailMergePropagateLabel", BindingFlags.InvokeMethod, null, wordBasic, null);
Option 2: All Office applications have access to the CommandBars
part of the shared object model. Originally, this was responsible for the menus and toolbars user interface (pre-Office 2007). In Office 2007 this was superceded by the Ribbon UI, the object was retained for backwards compatibility and expanded for limited interaction with the Ribbon UI.
One of these "new" methods is ExecuteMso
which executes a button command as if it had been clicked:
wdApp.CommandBars.ExecuteMso("MailMergeUpdateLabels");