1

I am trying to set MailMerge fields and let Word fill them for me, which isn't a Problem at all... What i am looking to do and can't figure out is the following, I want to set 2 MailMergeFields in 1 place and let Word sort it out for me.

In this case have a mergefield for PO_Box and Adress, if there is a PO_Box # use it, otherwise use the Standard Adress.

Example of the MailMerge what it would look like in Word:

{ IF { MERGEFIELD PO_Box } > "1" "{ MERGEFIELD PO_Box }" "{ MERGEFIELD Adress }" \* MERGEFORMAT }

Is there a way to make this happen thru some Word Interop Funktion?

Edit:

        static void Main(string[] args)
    {
        object fileName = @"C:\test.docx";
        string dataSource = @"C:\Test.csv";


        Word.Selection wrdSelection;
        Word.MailMerge wrdMailMerge;
        Word.MailMergeFields wrdMergeFields;

        // Start Word Application
        Word.Application wrdApp = new Word.Application();

        //Load a document
        Word.Document wrdDoc = wrdApp.Documents.Add(ref fileName, Visible: true);

        wrdSelection = wrdApp.Selection;
        wrdMailMerge = wrdDoc.MailMerge;

        // Open Data Source from .csv file
        wrdDoc.MailMerge.OpenDataSource(dataSource);


        //Create MergeFields
        wrdSelection.ParagraphFormat.Alignment = Word.WdParagraphAlignment.wdAlignParagraphLeft;
        wrdSelection.ParagraphFormat.LineSpacingRule = Word.WdLineSpacing.wdLineSpaceSingle;
        wrdSelection.ParagraphFormat.SpaceAfter = 0.0F;
        wrdMergeFields = wrdMailMerge.Fields;
        wrdMergeFields.Add(wrdSelection.Range, "Title");
        wrdSelection.TypeText(" ");
        wrdMergeFields.Add(wrdSelection.Range, "FirstName");
        wrdSelection.TypeText(" ");
        wrdMergeFields.Add(wrdSelection.Range, "LastName");
        wrdSelection.TypeParagraph();

        // Here I want to combine this Field with a PO_Box and let Word
        // do the trick
        wrdMergeFields.Add(wrdSelection.Range, "Address");
        wrdSelection.TypeParagraph();


        wrdMergeFields.Add(wrdSelection.Range, "City");
        wrdSelection.TypeText(", ");
        wrdMergeFields.Add(wrdSelection.Range, "State");
        wrdSelection.TypeText(" ");
        wrdMergeFields.Add(wrdSelection.Range, "Zip");
        wrdSelection.ParagraphFormat.LineSpacingRule = Word.WdLineSpacing.wdLineSpaceDouble;

        insertLines(wrdApp, 2);

        //Right justify the line and insert a date field with current date.
        wrdSelection.ParagraphFormat.Alignment = Word.WdParagraphAlignment.wdAlignParagraphRight;

        object objDate = "dd.MM.yyyy";
        wrdSelection.InsertDateTime(ref objDate);

        //Preview the final merge
        wrdDoc.MailMerge.Destination = Word.WdMailMergeDestination.wdSendToNewDocument;
        wrdDoc.MailMerge.Execute();

        //Close Template
        object saveOption = Word.WdSaveOptions.wdDoNotSaveChanges;
        wrdDoc.Close(ref saveOption);

        //Shows the Application after the process to the User
        wrdApp.Visible = true;
    }

    public static void insertLines(Word.Application wrdApp, int LineNum)
    {
        int iCount;

        // Insert "LineNum" blank lines.    
        for (iCount = 1; iCount <= LineNum; iCount++)
        {
            wrdApp.Selection.TypeParagraph();
        }
    }

So this basicly what I have, now i need the Adress MergeField to behave as i described above, since I will receive a .csv data from another programm that i can't modify I would like to place this field in Word that it will sort out if there is a PO Box or Adress.

Cindy Meister
  • 25,071
  • 21
  • 34
  • 43
  • Yes-and-no. It depends on how you're coding the assignment of content to the merge fields. If your approach is able to handle nested fields, then there are possibilities. If it can't, and you can change how your code works, then that; otherwise it needs something else. So please show us that code... – Cindy Meister Jan 27 '16 at 16:59
  • Ok added the Code, like i said i get a .csv data that will be tossed to word, and c# supposed to write the MergeFields for the whole MailMerge. – tim-o-matic Jan 28 '16 at 07:03
  • Thanks for the code. I've posted possible approaches below. A remark on your code: you could make it more reliable and "tighten it up" if you were to use the Range object instead of the Selection object. – Cindy Meister Jan 28 '16 at 15:19

1 Answers1

0

So, what you really want is to create the nested field codes. There are two basic approaches for this:

  1. Record a macro while doing it as a user as the basis. This relies on the Selection object, which can be tricky; the approach is not scalable (only works for that specific combination). This is described on StackOverflow, so I won't repeat it here: Setting up a nested field in Word using VBA
  2. Insert the field as a string, using "placeholders" to indicate where the field codes are, then convert the placeholders to field codes. This is scalable: it can be used for any combination of fields. There is an excellent algorithm in C# posted on GitHub, by Florian Wolters in response to a discussion in which I participated on MSDN. I copy it below for convenience.

https://gist.github.com/FlorianWolters/6257233

//------------------------------------------------------------------------------ 
// <copyright file="FieldCreator.cs" company="Florian Wolters"> 
//     Copyright (c) Florian Wolters. All rights reserved. 
// </copyright> 
// <author>Florian Wolters &lt;wolters.fl@gmail.com&gt;</author> 
//------------------------------------------------------------------------------ 
namespace FlorianWolters.Office.Word.Fields 
{ 
  using System; 
  using System.Collections; 
  using System.Runtime.InteropServices; 
  using Word = Microsoft.Office.Interop.Word; 

  /// <summary> 
  /// The class <see cref="FieldCreator"/> simplifies the creation of <see cref="Word.Field"/>s. 
  /// </summary> 
  public class FieldCreator 
  { 
     /// <summary> 
     /// Adds one or more new <see cref="Word.Field"/> to the specified <see cref="Word.Range"/>. 
     /// <para> 
     /// This method allows to insert nested fields at the specified range. 
     /// </para> 
     /// <example> 
     /// <c>InsertField(Application.Selection.Range, {{= {{PAGE}} - 1}};</c> 
     /// will produce 
     /// { = { PAGE } - 1 } 
     /// </example> 
     /// </summary> 
     /// <param name="range">The <see cref="Word.Range"/> where to add the <see cref="Word.Field"/>.</param> 
     /// <param name="theString">The string to convert to one or more <see cref="Word.Field"/> objects.</param> 
     /// <param name="fieldOpen">The special code to mark the start of a <see cref="Word.Field"/>.</param> 
     /// <param name="fieldClose">The special code to mark the end of a <see cref="Word.Field"/>.</param> 
     /// <returns>The newly created <see cref="Word.Field"/></returns> 
     /// <remarks> 
     /// A solution for VBA has been taken from <a href="http://stoptyping.co.uk/word/nested-fields-in-vba">this</a> 
     /// article and adopted for C# by the author. 
     /// </remarks> 
     public Word.Field InsertField( 
         Word.Range range, 
         string theString = "{{}}", 
         string fieldOpen = "{{", 
         string fieldClose = "}}") 
     { 
         if (null == range) 
         { 
             throw new ArgumentNullException("range"); 
         } 

         if (string.IsNullOrEmpty(fieldOpen)) 
         { 
             throw new ArgumentException("fieldOpen"); 
         }  

         if (string.IsNullOrEmpty(fieldClose)) 
         { 
             throw new ArgumentException("fieldClose"); 
         } 

         if (!theString.Contains(fieldOpen) || !theString.Contains(fieldClose)) 
         { 
             throw new ArgumentException("theString"); 
         } 

         // Special case. If we do not check this, the algorithm breaks. 
         if (theString == fieldOpen + fieldClose) 
         { 
             return this.InsertEmpty(range); 
         } 

         // TODO Implement additional error handling. 
         // TODO Possible to remove the dependency to state capture? 
         using (new StateCapture(range.Application.ActiveDocument)) 
         { 
             Word.Field result = null; 
             Stack fieldStack = new Stack(); 

             range.Text = theString; 
             fieldStack.Push(range); 

             Word.Range searchRange = range.Duplicate; 
             Word.Range nextOpen = null; 
             Word.Range nextClose = null; 
             Word.Range fieldRange = null; 

             while (searchRange.Start != searchRange.End) 
             { 
                 nextOpen = this.FindNextOpen(searchRange.Duplicate, fieldOpen); 
                 nextClose = this.FindNextClose(searchRange.Duplicate, fieldClose); 

                 if (null == nextClose) 
                 { 
                     break; 
                 } 

                 // See which marker comes first. 
                 if (nextOpen.Start < nextClose.Start) 
                 { 
                     nextOpen.Text = string.Empty; 
                     searchRange.Start = nextOpen.End; 

                     // Field open, so push a new range to the stack. 
                     fieldStack.Push(nextOpen.Duplicate); 
                 } 
                 else 
                 { 
                     nextClose.Text = string.Empty; 

                     // Move start of main search region onwards past the end marker. 
                     searchRange.Start = nextClose.End; 

                     // Field close, so pop the last range from the stack and insert the field. 
                     fieldRange = (Word.Range)fieldStack.Pop(); 
                     fieldRange.End = nextClose.End; 
                     result = this.InsertEmpty(fieldRange); 
                 } 
             } 

             // Move the current selection after all inserted fields. 
             // TODO Improvement possible, e.g. by using another range object? 
             int newPos = fieldRange.End + fieldRange.Fields.Count + 1; 
             fieldRange.SetRange(newPos, newPos); 
             fieldRange.Select(); 

             // Update the result of the outer field object. 
             result.Update(); 


             return result; 
         } 
     } 

     /// <summary> 
     /// Adds a new empty <see cref="Word.Field"/> to the specified <see cref="Word.Range"/>. 
     /// </summary> 
     /// <param name="range">The <see cref="Word.Range"/> where to add the <see cref="Word.Field"/>.</param> 
     /// <param name="preserveFormatting"> 
     /// Whether to apply the formatting of the previous <see cref="Word.Field"/> result to the new result. 
     /// </param> 
     /// <returns>The newly created <see cref="Word.Field"/>.</returns> 
     public Word.Field InsertEmpty(Word.Range range, bool preserveFormatting = false) 
     { 
         Word.Field result = this.AddFieldToRange(range, Word.WdFieldType.wdFieldEmpty, preserveFormatting); 

         // Show the field codes of an empty field, because otherwise we can't be sure that it is visible. 
         result.ShowCodes = true; 

         return result; 
     } 

     /// <summary> 
     /// Creates a <see cref="Word.Field"/> and adds it to the specified <see cref="Word.Range"/> 
     /// </summary> 
     /// <remarks> 
     /// The <see cref="Word.Field"/> is added to the <see cref="Word.Fields"/> collection of the specified <see 
     /// cref="Word.Range"/>. 
     /// </remarks> 
     /// <param name="range">The <see cref="Word.Range"/> where to add the <see cref="Word.Field"/>.</param> 
     /// <param name="type">The type of <see cref="Word.Field"/> to create.</param> 
     /// <param name="preserveFormatting"> 
     /// Whether to apply the formatting of the previous field result to the new result. 
     /// </param> 
     /// <param name="text">Additional text needed for the <see cref="Word.Field"/>.</param> 
     /// <returns>The newly created <see cref="Word.Field"/>.</returns> 
     private Word.Field AddFieldToRange( 
         Word.Range range, 
         Word.WdFieldType type, 
         bool preserveFormatting = false, 
         string text = null) 
     { 
         return range.Fields.Add( 
             range, 
             type, 
             (null == text) ? Type.Missing : text, 
             preserveFormatting); 
     } 

     private Word.Range FindNextOpen(Word.Range range, string text) 
     { 
         Word.Find find = this.CreateFind(range, text); 
         Word.Range result = range.Duplicate; 

         if (!find.Found) 
         { 
             // Make sure that the next closing field will be found first. 
             result.Collapse(Word.WdCollapseDirection.wdCollapseEnd); 
         } 

         return result; 
     } 

     private Word.Range FindNextClose(Word.Range range, string text) 
     { 
         return this.CreateFind(range, text).Found ? range.Duplicate : null; 
     } 

     private Word.Find CreateFind(Word.Range range, string text) 
     { 
         Word.Find result = range.Find; 
         result.Execute(FindText: text, Forward: true, Wrap: Word.WdFindWrap.wdFindStop); 

         return result; 
     } 
  } 
} 
Community
  • 1
  • 1
Cindy Meister
  • 25,071
  • 21
  • 34
  • 43
  • TY very much, i found that just after you mentioned the nested fields. Only thing where he uses the StateCapture is confusing for me, what does this do, Google didn't give me any explanantion to the class, though it seems that it works without StateCapture as well – tim-o-matic Jan 28 '16 at 15:20
  • using (new StateCapture(range.Application.ActiveDocument)) { its in the method InsertFields, – tim-o-matic Jan 29 '16 at 07:13
  • I have no idea what StateCapture is supposed to do. There is a comment about seeing whether it will work without, so FW must have suspected it's not necessary. When I've used a similar approach (VBA) I haven't needed anything beyond what is used here. Possibly, this was something to roll back the actions if there was failure? – Cindy Meister Jan 29 '16 at 16:40