none
Adding Mulitple Pages from Different Word Templates to a Single Word Document RRS feed

  • Question

  • Basically, I have a List of unprinted letters that I would like to populate into a single word document with multiple pages based on different templates. Right now my code creates a word document for each one as it goes through the loop. The gist of my code is this:

    public static void PrintNow(List<Letter> letters) { var word = new Microsoft.Office.Interop.Word.Application(); var doc = new Microsoft.Office.Interop.Word.Document(); foreach (var letter in letters) { var templatePath = "C:/Templates/" + letter.Type + ".docx"; doc = word.Documents.Add(Template: templatePath); foreach (Field field in doc.Fields) { if (field.Code.Text.Contains("NameAddress")) { field.Select(); word.Selection.TypeText(letter.NameAddress); } } }

    word.Visible = true; }

    Any direction would be greatly appreciated. Thanks.





    • Edited by Matt.Brown Monday, March 28, 2016 10:00 AM
    • Moved by CoolDadTx Monday, March 28, 2016 2:36 PM Office related
    Monday, March 28, 2016 9:53 AM

Answers

  • SQL isn't my strong point, but the following works with an Excel data source:

    SQLStatement:="SELECT * FROM [Letters$] WHERE ([HasBeenPrinted]=FALSE)"


    Cheers
    Paul Edstein
    [MS MVP - Word]

    • Marked as answer by Matt.Brown Thursday, March 31, 2016 9:27 AM
    Thursday, March 31, 2016 9:23 AM
  • With VBA (I don't do C#), you might use code like:

    Sub Run_Merge()
    Dim wdApp As New Word.Application, wdDoc As Word.Document
    Const StrFlNm As String = "Filename for output document"
    Const StrMMFlNm As String = "Mailmerge main document name"
    Const StrMMPath As String = "Path to mailmerge main document"
    Const StrSrc As String = "Path to database\DATABASE.mdb"
    With wdApp
      .DisplayAlerts = wdAlertsNone
      Set wdDoc = .Documents.Open(FileName:=StrMMPath & "\" & StrMMFlNm, AddToRecentFiles:=False)
    End With
    With wdDoc
      With .MailMerge
        .MainDocumentType = wdFormLetters
        .Destination = wdSendToNewDocument
        .SuppressBlankLines = True
        .OpenDataSource Name:=StrSrc, ConfirmConversions:=True, ReadOnly:=False, LinkToSource:=False, _
          AddToRecentFiles:=False, PasswordDocument:="", PasswordTemplate:="", WritePasswordDocument:="", _
          WritePasswordTemplate:="", Revert:=False, Format:=wdOpenFormatAuto, Connection:="", _
          SQLStatement:="SELECT * FROM letters WHERE(type='type' AND userID='userID' AND isPrinted=false)", _
          SubType:=wdMergeSubTypeAccess
        .Execute Pause:=False
        With ActiveDocument
          .SaveAs FileName:=StrMMPath & "\" & StrFlNm, FileFormat:=wdFormatDocument, AddToRecentFiles:=False
          .Close Savechanges:=False
        End With
        .MainDocumentType = wdNotAMergeDocument
      End With
      wdApp.DisplayAlerts = wdAlertsAll
      .Close Savechanges:=False
    End With
    End Sub


    Cheers
    Paul Edstein
    [MS MVP - Word]

    • Marked as answer by Matt.Brown Wednesday, March 30, 2016 1:01 PM
    Wednesday, March 30, 2016 11:58 AM
  • I didn't mean to take so to post my C#. Maybe it'll help someone:

    var word = new Microsoft.Office.Interop.Word.Application();
    var doc = new Microsoft.Office.Interop.Word.Document();
    
    // Full name of merge template
    var mailmergepath = @"s:\mattbrown\scripts\Letters\template.docx";
    // Full name of database
    var dbpath = @"s:\mattbrown\scripts\LetterDB\lettersDB.mdb";
    // SQL Query for merge. Looks like:
    // "SELECT * FROM [letters] WHERE([HasBeenPrinted]=false 
    //      AND [LetterType]='lettertype' AND [UserID]='userID')"
    var query = "SELECT * FROM [letters] WHERE([HasBeenPrinted]=false " + 
        "AND [LetterType]='" + selected + "' AND [UserID]='" + userID + "')";
    
    // Opens merge template
    doc = word.Documents.Open(FileName: mailmergepath, AddToRecentFiles: false);
    
    doc.MailMerge.MainDocumentType = WdMailMergeMainDocType.wdFormLetters;
    doc.MailMerge.Destination = WdMailMergeDestination.wdSendToNewDocument;
    doc.MailMerge.SuppressBlankLines = true;
    
    doc.MailMerge.OpenDataSource(
        Name: dbpath,
        ConfirmConversions: true,
        ReadOnly: false,
        LinkToSource: false,
        AddToRecentFiles: false,
        PasswordDocument: "",
        PasswordTemplate: "",
        WritePasswordDocument: "",
        WritePasswordTemplate: "",
        Revert: false,
        Format: WdOpenFormat.wdOpenFormatAuto,
        Connection: "",
        SQLStatement: query,
        SubType: WdMergeSubType.wdMergeSubTypeAccess);
    
    doc.MailMerge.Execute(false);
    
    // Closes merge template
    doc.Close(SaveChanges: false);
    
    // Leaves application not visible until merge is complete
    word.Visible = true;



    • Edited by Matt.Brown Sunday, April 3, 2016 10:11 AM Formatting looked off
    • Marked as answer by Matt.Brown Sunday, April 3, 2016 10:12 AM
    Sunday, April 3, 2016 10:07 AM

All replies

  • Your description suggests these files might be saved copies of mailmerge previews. If so, why not simply run a merge that sends the output for these records to a single file?

    Do note, too, that the content of mailmerge previews copied to any document not connected to the original data source are liable to revert to the original, raw mailmerge fields or, if the destination document is connected to a different data source, are liable to result in mergefield errors for any field names that don't exist in both sources and, where they do, could result in the wrong data being merged if the fields don't have the same content.


    Cheers
    Paul Edstein
    [MS MVP - Word]

    Tuesday, March 29, 2016 2:38 AM
  • >>>Basically, I have a List of unprinted letters that I would like to populate into a single word document with multiple pages based on different templates.

    According to your description, please correct me if I have any misunderstandings on your question, I suggest that you could use Macro to merge mulitple Word document into one Word document, you could refer to below code:
    Sub MergeDocuments()
        Application.ScreenUpdating = False
            MyPath = ActiveDocument.Path
            MyName = Dir(MyPath & "\" & "*.docx")
            i = 0
            Do While MyName <> ""
            If MyName <> ActiveDocument.Name Then
            Set wb = Documents.Open(MyPath & "\" & MyName)
            Selection.WholeStory
            Selection.Copy
            Windows(1).Activate
            Selection.EndKey Unit:=wdLine
            Selection.TypeParagraph
            Selection.Paste
            Selection.InsertBreak wdPageBreak
            i = i + 1
            wb.Close False
            End If
            MyName = Dir
            Loop
        Application.ScreenUpdating = True
    End Sub

    Tuesday, March 29, 2016 2:53 AM
  • Thank you for the reply. Let me add some clarification:

    I have users who will generate various, unprinted letters throughout a day. The data for these letters (the merge fields are identical in each template) are stored in an Access database.

    At the end of the day (or whenever) these records would need to be printed according to the template for each. Ideally I would like to mailmerge all of these letters into a single file so that the user can print them all at once. Right now, a single file is created for each record.

    I know could save each file and merge them together afterward, but I was hoping to find a way to avoid that. Thanks again!

    Tuesday, March 29, 2016 8:28 AM
  • Thank you for the reply. Let me add some clarification:

    I have users who will generate various, unprinted letters throughout a day. The data for these letters (the merge fields are identical in each template) are stored in an Access database.

    At the end of the day (or whenever) these records would need to be printed according to the template for each. Ideally I would like to mailmerge all of these letters into a single file so that the user can print them all at once. Right now, a single file is created for each record.

    I know could save each file and merge them together afterward, but I was hoping to find a way to avoid that. Thanks again!

    Tuesday, March 29, 2016 8:28 AM
  • In which case, as I indicated previously, you could simply run the merge, filtering for all the records you want, and send the combined output to a single Word document. This facility is built into Word and you don't need any code for it. The only time code might be required is if there are separate mailmerge main documents being used to generate different kinds of letters. Even then, however, a single appropriately-configured mail-merge main document could combine the different letter types into a single document.

    Cheers
    Paul Edstein
    [MS MVP - Word]

    Tuesday, March 29, 2016 9:29 AM
  • Thanks again. That's what I have, namely multiple word document templates (though they all will have the same merge field names).

    I guess I was hoping that there would be a simple way to achieve this (e.g. doc.AddPage(Template: path);), but I guess there's not.

    Thanks again for the suggestions. I'll post my solution once I find it. Matt

    Tuesday, March 29, 2016 9:49 AM
  • Hi, Matt.Brown

    According to your description, you could use INCLUDETEXT field to insert data from another documents, refer to below code:
    ActiveDocument.Fields.Add _
    Range:=Selection.Range, _
    Type:=wdFieldIncludeText, _
    Text:="another document location", _
    PreserveFormatting:=True

    Tuesday, March 29, 2016 9:54 AM
  • The only thing adding an INCLUDETEXT field via a macro achieves is linking to an external file. Delete or rename that file and the INCLUDETEXT field falls over. Where an INCLUDETEXT field might be beneficial is as part of the mailmerge main document itself - but only if there is a need to incorporate content from a separate document with its own MERGEFIELDs, for example - and one wouldn't do that one-off exercise with VBA.


    Cheers
    Paul Edstein
    [MS MVP - Word]

    Tuesday, March 29, 2016 12:13 PM
  • Do you know if there is a way to programmatically (C#) begin a Word mail merge from an SQL query on an Access database? This would basically accomplish the what I'm trying to accomplish.

    So, the order I'm thinking:

    1. Query Access db: "SELECT * FROM letters WHERE(type='type' AND userID='userID' AND isPrinted=false)"

    2. Mail Merge that Query with type.docx

    Thanks again, Matt

     
    Wednesday, March 30, 2016 10:03 AM
  • With VBA (I don't do C#), you might use code like:

    Sub Run_Merge()
    Dim wdApp As New Word.Application, wdDoc As Word.Document
    Const StrFlNm As String = "Filename for output document"
    Const StrMMFlNm As String = "Mailmerge main document name"
    Const StrMMPath As String = "Path to mailmerge main document"
    Const StrSrc As String = "Path to database\DATABASE.mdb"
    With wdApp
      .DisplayAlerts = wdAlertsNone
      Set wdDoc = .Documents.Open(FileName:=StrMMPath & "\" & StrMMFlNm, AddToRecentFiles:=False)
    End With
    With wdDoc
      With .MailMerge
        .MainDocumentType = wdFormLetters
        .Destination = wdSendToNewDocument
        .SuppressBlankLines = True
        .OpenDataSource Name:=StrSrc, ConfirmConversions:=True, ReadOnly:=False, LinkToSource:=False, _
          AddToRecentFiles:=False, PasswordDocument:="", PasswordTemplate:="", WritePasswordDocument:="", _
          WritePasswordTemplate:="", Revert:=False, Format:=wdOpenFormatAuto, Connection:="", _
          SQLStatement:="SELECT * FROM letters WHERE(type='type' AND userID='userID' AND isPrinted=false)", _
          SubType:=wdMergeSubTypeAccess
        .Execute Pause:=False
        With ActiveDocument
          .SaveAs FileName:=StrMMPath & "\" & StrFlNm, FileFormat:=wdFormatDocument, AddToRecentFiles:=False
          .Close Savechanges:=False
        End With
        .MainDocumentType = wdNotAMergeDocument
      End With
      wdApp.DisplayAlerts = wdAlertsAll
      .Close Savechanges:=False
    End With
    End Sub


    Cheers
    Paul Edstein
    [MS MVP - Word]

    • Marked as answer by Matt.Brown Wednesday, March 30, 2016 1:01 PM
    Wednesday, March 30, 2016 11:58 AM
  • Thanks! I think I translate the VBA easily enough. I'll try it out and post the results in C#. Thanks again. Matt

    • Edited by Matt.Brown Wednesday, March 30, 2016 1:01 PM
    Wednesday, March 30, 2016 1:01 PM
  • Thanks again. You've gotten me a lot closer. I'm having an issue with my SQL query, and it appears I missing something in way of syntax, because my standard SQL query throws an exception, while removing the query merges the entire database with no exception. This is what I have:

    var word = new Microsoft.Office.Interop.Word.Application();
    var doc = new Microsoft.Office.Interop.Word.Document();
    var mailmergepath = @"s:\LetterDB\mergedoc.docx";
    var dbpath = @"s:\LetterDB\lettersDB.mdb";
    
    word.Visible = true;
    
    doc = word.Documents.Open(FileName: mailmergepath, AddToRecentFiles: false);
    
    doc.MailMerge.MainDocumentType = WdMailMergeMainDocType.wdFormLetters;
    doc.MailMerge.Destination = WdMailMergeDestination.wdSendToNewDocument;
    doc.MailMerge.SuppressBlankLines = true;
    
    doc.MailMerge.OpenDataSource(
        Name: dbpath, 
        ConfirmConversions: true, 
        ReadOnly: false, 
        LinkToSource: false,
        AddToRecentFiles: false, 
        PasswordDocument: "", 
        PasswordTemplate: "", 
        WritePasswordDocument: "",
        WritePasswordTemplate: "", 
        Revert: false, 
        Format: WdOpenFormat.wdOpenFormatAuto, 
        Connection: "",
        SQLStatement: "SELECT * FROM letters WHERE(HasBeenPrinted=false)", // This query is throwing a COMException
        SubType: WdMergeSubType.wdMergeSubTypeAccess);
    And I've changed up the query to test it, and each time I get the exception. Thanks again for your input.

    Also, I got this off of MSDN regard the Connection parameter, though I can't find any examples on how to use it:

    Connection

    Optional Object. A range within which the query specified by SQLStatement is to be performed. (See Remarks below.) How you specify the range depends on how data is retrieved. For example:

    • When retrieving data through Open Database Connectivity (ODBC), you specify a connection string.

    • When retrieving data from Microsoft Excel using dynamic data exchange (DDE), you specify a named range.

    Dynamic data exchange (DDE) is an older technology that is not secure. If possible, use a more secure alternative to DDE.

    • When retrieving data from Microsoft Access, you specify the word "Table" or "Query" followed by the name of a table or query.



    • Edited by Matt.Brown Thursday, March 31, 2016 9:18 AM
    Thursday, March 31, 2016 9:05 AM
  • SQL isn't my strong point, but the following works with an Excel data source:

    SQLStatement:="SELECT * FROM [Letters$] WHERE ([HasBeenPrinted]=FALSE)"


    Cheers
    Paul Edstein
    [MS MVP - Word]

    • Marked as answer by Matt.Brown Thursday, March 31, 2016 9:27 AM
    Thursday, March 31, 2016 9:23 AM
  • THAT WAS IT! Thanks again. You have been a most tremendous help.
    Thursday, March 31, 2016 9:27 AM
  • I should note that since it was Access, the table doesn't have the $ character at the end:

    "SELECT * FROM [letters] WHERE([HasBeenPrinted]=false)"
    

    Thursday, March 31, 2016 9:31 AM
  • I didn't mean to take so to post my C#. Maybe it'll help someone:

    var word = new Microsoft.Office.Interop.Word.Application();
    var doc = new Microsoft.Office.Interop.Word.Document();
    
    // Full name of merge template
    var mailmergepath = @"s:\mattbrown\scripts\Letters\template.docx";
    // Full name of database
    var dbpath = @"s:\mattbrown\scripts\LetterDB\lettersDB.mdb";
    // SQL Query for merge. Looks like:
    // "SELECT * FROM [letters] WHERE([HasBeenPrinted]=false 
    //      AND [LetterType]='lettertype' AND [UserID]='userID')"
    var query = "SELECT * FROM [letters] WHERE([HasBeenPrinted]=false " + 
        "AND [LetterType]='" + selected + "' AND [UserID]='" + userID + "')";
    
    // Opens merge template
    doc = word.Documents.Open(FileName: mailmergepath, AddToRecentFiles: false);
    
    doc.MailMerge.MainDocumentType = WdMailMergeMainDocType.wdFormLetters;
    doc.MailMerge.Destination = WdMailMergeDestination.wdSendToNewDocument;
    doc.MailMerge.SuppressBlankLines = true;
    
    doc.MailMerge.OpenDataSource(
        Name: dbpath,
        ConfirmConversions: true,
        ReadOnly: false,
        LinkToSource: false,
        AddToRecentFiles: false,
        PasswordDocument: "",
        PasswordTemplate: "",
        WritePasswordDocument: "",
        WritePasswordTemplate: "",
        Revert: false,
        Format: WdOpenFormat.wdOpenFormatAuto,
        Connection: "",
        SQLStatement: query,
        SubType: WdMergeSubType.wdMergeSubTypeAccess);
    
    doc.MailMerge.Execute(false);
    
    // Closes merge template
    doc.Close(SaveChanges: false);
    
    // Leaves application not visible until merge is complete
    word.Visible = true;



    • Edited by Matt.Brown Sunday, April 3, 2016 10:11 AM Formatting looked off
    • Marked as answer by Matt.Brown Sunday, April 3, 2016 10:12 AM
    Sunday, April 3, 2016 10:07 AM