Search code examples
c#ms-wordoffice-interopmailmerge

C# Word Interop: using built-in "Update Labels" function in Mailings


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:

  1. Trigger the same method that the Update Labels button uses through Word Interop.

    • I've done a deep dive through the MSDN reference for Word Interop in C#. I have found no such method (and no, myObject.Range.Fields.Update() does not accomplish this.).
  2. Reference the button and simulate a click.

    • Something like 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.
  3. Write a method that achieves the same result. After all, I might be chasing the XY Problem.

    • I already wrote one. Unfortunately, it's very slow (~7 seconds for a page of 30 labels). The most time-consuming operation seems to be adding the fields to the ranges. The "Update Labels" button's effect is very flexible and near-instantaneous.

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.


Solution

  • 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");