Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / DevOps / automation

Using Template Bookmarks for Automating Microsoft Word Reports

5.00/5 (2 votes)
31 May 2018CPOL7 min read 24.7K   511  
A C# method is discussed for using bookmarks in Microsoft Word templates to automate reports.

Image 1

Figure 1 - Example of an automated report built from a template

Introduction

A C# method is discussed for using bookmarks in Microsoft Word templates to automate reports. Automating reports using templates can enable the user or client to include custom designs and information. For example, the company’s logo design can be put into the template header. Each client can have their own template report style. However, some information may need to be placed in specific locations in the report, or there may be user input to insert, that is only known just prior to the report being generated, and this is where bookmarks can be used.

In the above example report, bookmarks were placed into the template for the test date, subtitle, project no., test method, customer, engineer/technician, and the information in the Test Setup table. A template can be designed in Microsoft Word and bookmarks inserted at the desired locations. The report maker application can then read the bookmarks from the template and insert the text at the bookmark location. Some known information, like the current date can be inserted automatically, or for specific user information, a textbox dialog can prompt the user for the text to be inserted for each bookmark field. When a bookmark is defined in Word, it must be given a name, and these names can be used as textbox labels to identify each bookmark.

A bookmark may be inserted into a Microsoft Word 2013 template as follows:

  • Position the cursor to the desired bookmark position in the document
  • Select the ribbon “INSERT” tab
  • From the “Links” dropdown select “Bookmark
  • Type the name of the bookmark (note: spaces in the name are not allowed)
  • Click Add

(To make bookmarks visible: go to File > Options > Advanced, then under “Show document content” check “Show bookmarks”).

Using the Code

The code below reads the bookmarks from a Word template document and returns them in top-down order in a List<string>.

C#
using System.Linq; // For LINQ .OrderBy
using Microsoft.Office.Interop.Word; // For _Document, _Application, Bookmark

    //--------------------------------------------------------------------------
    // Returns ordered list of bookmarks found in doc.
    //--------------------------------------------------------------------------
    public static List<string> GetBookmarks(string fname) {
        _Application WDapp = new Microsoft.Office.Interop.Word.Application();
        _Document doc; // _ removes compiler ambiguity warning on Close()
        List<Bookmark> bmarks = new List<Bookmark>();

        try {
            doc = WDapp.Documents.Add(fname);

            // Get list as they come from Word (alphabetically)
            foreach (Bookmark b in doc.Bookmarks) {
                bmarks.Add(b);
            }
        }
        catch (Exception err) {
            MessageBox.Show("Error: " + err.Message, "GetBookmarks()", MessageBoxButtons.OK,
		MessageBoxIcon.Error);
            return null;
        }
     
        // Re-sort list in order of appearance
        bmarks = bmarks.OrderBy(b => b.Start).ToList(); // LINQ
      
        List<string> slBMarks = new List<string>();
        foreach (Bookmark b in bmarks) {
            slBMarks.Add(b.Name); // Accumulate bookmark names
        }

        try { // Crashes if already closed
            doc.Close(WdSaveOptions.wdDoNotSaveChanges);
            WDapp.Quit(WdSaveOptions.wdDoNotSaveChanges);
        }
        catch { }

        return slBMarks;
    }

The template file name is passed in the variable fname. The bookmarks are read from the Word template and added to a List<Bookmark> object. Since the bookmarks are returned from Word in alphabetical order, the LINQ statement re-sorts the bookmarks in reading order (i.e., top-down as found in the template). The method returns the ordered bookmark names in a List<string> object. Once the bookmark names are known, the class method InsertBookmarkText (shown below) can be called to insert the text at the bookmark location.

C#
//--------------------------------------------------------------------------
// Inserts text at bookmark, returns true if didn't fail
//--------------------------------------------------------------------------
private bool InsertBookmarkText(string bookmarkName, string text) {
    try {
        if (_doc.Bookmarks.Exists(bookmarkName)) {
            _doc.Bookmarks.get_Item(bookmarkName).Range.Text = text;
            return true;
        }
    }
    catch (Exception ex) {
        #if DEBUG
            MessageBox.Show(ex.Message, "InsertBookmarkText()", MessageBoxButtons.OK,
            MessageBoxIcon.Error);
        #endif
    }

    return false; // Error return
}

The method takes the bookmark name and text to be inserted at that bookmark position as input. The name of the bookmark is used by the method to locate the bookmark in the document. Note that _doc used here is a _Document object as in GetBookmarks above, and has already been initialized before the call to this method by the class constructor. The exception “catch” here only displays a message in Debug mode, because not finding the bookmark does not normally warrant pausing the application to show the error. For example, the code could use this method to search for several different bookmarks, that are used in some templates but not others, and only insert text for the ones found.

For information that must be prompted for, a dialog can be generated to prompt the user for the text to be inserted at each bookmark.

Image 2

Figure 2 - A dynamic textbox dialog that updates itself based on the selected template file

The dialog above was designed with a combo box to select the template file to be used for the report. Since each template may contain different bookmarks, the dialog was made to automatically insert textboxes for only the bookmarks that are found. When the template file is chosen from the combo box, the textboxes are updated to reflect just the ones found in that template. It uses the bookmark names for the textbox labels so the user can identify which bookmark item it refers to. A “Panel” control is utilized as the container for all the textboxes so that textbox updates can be done easily by simply using the panel control’s Clear method. The panel can also be anchored (properties: Top, Bottom, Left) to the form for automatic resizing.

The Dialog method below (see the CReportDlg class) sets up the dialog. The private member variables _path and _templatePath are saved for use by other class methods. The FilesInDir method (see CReportDlg class) returns a List<string> of files found matching the file mask in the passed folder, which in this case are template files, and these file names are then added to the combo box drop down.

C#
//--------------------------------------------------------------------------
// Init. dialog
//--------------------------------------------------------------------------
public List<string> Dialog() {
    System.Windows.Forms.Cursor.Current = Cursors.WaitCursor;

    _path = System.Windows.Forms.Application.StartupPath + "\\";
    _templatePath = Path.Combine(_path, _templatePath); // Append def. filename
    string fname = Path.GetFileName(_templatePath);

    // Get template files for combo box
    List<string> tfiles = FilesInDir(_path, new string[] { "*.dotx" });
    if (tfiles != null) {
        foreach (string f in tfiles) {
            this.cbxTemplate.Items.Add(f);
        }

        if (fname != "")
            cbxTemplate.Text = fname; // Last one used
        else if (cbxTemplate.Items.Count > 0)
            cbxTemplate.Text = cbxTemplate.Items[0].ToString(); // Def. to 1st one
    }

    System.Windows.Forms.Cursor.Current = Cursors.Default;
    ShowDialog();

    return _lResults;
}

The code below is the combo box event handler for when the user changes to a different template file. As mentioned above, a panel is used as the container for the textboxes (called pnlBookmarks) so that all the previous textboxes can be easily disposed of (by calling Clear), and also the panel can then be docked to the form for automatic resizing when the textboxes are changed. The bookmarks are read from the new template and a textbox with label is added for each bookmark in the foreach loop. The call to CMsgDlg.AddTextBox adds the textboxes to the panel. The panel control is passed to the CMsgDlg constructor so that the AddTextBox method can access the control to be used for putting the textboxes onto. For more information, and to download the CMsgDlg class, see this CodeProject article.

The foreach loop labeled with the comment: “Check for prev. bookmark defaults” checks for previously entered textbox entries to provide defaults to the user for ease of use. This check is made by looking at the textbox tags which are used to hold the bookmark names. The AddTextBox method stores the passed textbox label, which in this case, is the bookmark name, in the textbox tag for this purpose of identification. If the new bookmark name matches the textbox tag, then the previously saved textbox string is used for the default input. In the CReportDlg.btnOK_Click function, the bookmark name and the text entered by the user is appended together in one string (to be returned to the caller), separated by a ‘|’ character for parsing later. This is why the Split(‘|’) is done to parse out the bookmark name. The textbox positions and maximum width is checked so they can be aligned, and then finally the dialog’s form is sized for proper fitting of the textboxes.

C#
//--------------------------------------------------------------------------
// cbxTemplate change handler sets up textboxes for bookmarks.
// A panel is used for bookmark controls for easier updating.
//--------------------------------------------------------------------------
private void cbxTemplate_SelectedIndexChanged(object sender, EventArgs e) {
    pnlBookmarks.Controls.Clear(); // Remove all prev.
    this.Size = new Size(_startformWidth, _startformHeight);

    // Add new bookmarks textboxes
    List<string> bms = CReport.GetBookmarks(Path.Combine(_path, this.cbxTemplate.Text));
    if (bms != null && bms.Count > 0) {
        string strPad = string.Format("{0:-30}\n ", " "); // Default empty string
        int maxlblRight = 0;
        foreach (string bookmarkName in bms) {
            string strDef = strPad;

            // Check for prev. bookmark defaults
            foreach (string bm_text in _lbmks) {
                string[] str = bm_text.Split('|');
                if (str[0] == bookmarkName) {
                    if (str[1] != "") strDef = str[1];
                    break;
                }
            }

            Control tbx = new CMsgDlg(pnlBookmarks).AddTextBox(bookmarkName + ":", strDef);
            tbx.Text = tbx.Text.Trim(new char[] { ' ', '\n', '\r' }); // Trim \n's
            tbx.Name = bookmarkName; // Name for return ID
            Label lbl = (Label)tbx.Tag; // CMsgDlg stores tbx label object in Tag
            lbl.Left = 0; // Align labels
            if (maxlblRight < lbl.Right) maxlblRight = lbl.Right; // Save max.
            tbx.Left = lbl.Right;
            tbx.Width = this.Width - _margin * 2; // Make to form width
        }
        this.Width = pnlBookmarks.Width + pnlBookmarks.Margin.Right + _margin * 2;

        // Re-align all to max lbl.Right
        foreach (Control ctl in pnlBookmarks.Controls) {
            if (ctl is TextBox) ctl.Left = maxlblRight;
        }
    }

    this.Height = _startformHeight + pnlBookmarks.Height;
}

The CMsgDlg.AddTextBox method is called in a loop to add the appropriate number of textboxes and their labels as prompts for the bookmark information to be inserted into the report. Its method is declared as:

C#
public TextBox AddTextBox(string lblText, string tbxText) {...

The first passed parameter, lblText, will become the label for the textbox. Here, the bookmark name followed by a colon (“:”) is used. The second parameter, tbxText, is passed to the method to use as a default text for the textbox. It's also used to calculate the width for the textbox, and number of lines it should have (by number of embedded ‘\n’). In the usage above, the default textbox string strPad, is padded to 30 characters to provide a wider textbox for input, and the ‘\n’ is added to make the textbox have two lines. For more information, and to download the CMsgDlg class, see this CodeProject article.

After the user has entered the desired text for each bookmark and closed the dialog, the InsertBookmarkText method (discussed above) is used to insert the text strings. This method is called repeatedly in a loop to insert each text string at its proper bookmark position (see the CReport method for more details).

Conclusion

It can be seen that using bookmarks with templates for automating reports can be a useful option for the developer. The methods discussed here will hopefully offer a good starting point for utilizing these features.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)