TX Text Control .NET for WPF

Creating Documents

Topic

Letters, invoices and reports that a WPF application writes itself begin as an empty document, either in the TextControl of the window or in a ServerTextControl that the user never sees. Your code clears the editor for a new document, sets the page format, assembles text, HTML snippets and whole files, stores the document properties and switches off the undo history while it builds large documents.

The first steps are in Create a Document Programmatically, which inserts formatted text, a table, an image and a header. Document Object Model explains how a document is organized. Each example below is the Click handler of a button in MainWindow.xaml, such as <Button Content="New" Click="NewButton_Click" />, next to a TextControl named textControl1. MainWindow.xaml.cs needs these using directives:

using System.Windows;
using TXTextControl;

Start a New Document

ResetContents deletes the entire contents of the TextControl. The page size and margins are settings of the control and stay as they are, so assign new values when a new document should start with the defaults:

private void NewButton_Click(object sender, RoutedEventArgs e)
{
    // Remove the contents and start with US letter pages and 1-inch margins
    textControl1.ResetContents();
    textControl1.PageSize = new PageSize();
    textControl1.PageMargins = new PageMargins();
}

The PageSize constructor without parameters creates US letter pages (8.5 x 11 inches), and the PageMargins constructor without parameters creates margins of 1 inch. Unlike the Windows Forms control, WPF.TextControl has no ResetPageSize and ResetPageMargins methods. How to ask the user before unsaved changes are lost is shown in Load and Save Documents.

A ServerTextControl begins with an empty document. Create must be called before any other member and returns false when the instance couldn't be initialized; a using statement disposes it afterward. Using ServerTextControl in Desktop Applications describes the lifecycle.

Set the Page Format

In WPF, PageSize and PageMargins are measured in device-independent units of 1/96 inch by default, the unit of PageUnit. The following handler shows the current format and then sets margins of half an inch and landscape pages:

private void PageFormatButton_Click(object sender, RoutedEventArgs e)
{
    // Values in 1/96 inch, the default PageUnit of WPF
    MessageBox.Show($"Page size: {textControl1.PageSize.Width} x {textControl1.PageSize.Height}\r\n" +
        $"Margins: {textControl1.PageMargins.Left}, {textControl1.PageMargins.Top}, " +
        $"{textControl1.PageMargins.Right}, {textControl1.PageMargins.Bottom}");

    // Margins of 0.5 inch (48/96 inch) on every side and landscape orientation
    textControl1.PageMargins = new PageMargins(48, 48, 48, 48);
    textControl1.Landscape = true;
}

The PageMargins constructor takes the left, top, right and bottom margin in the current unit, and Landscape turns the pages. PageUnit also offers millimeters, 1/100 inch and twips. A ServerTextControl has the same properties. Parts of a document that need their own page format, such as one landscape page in a portrait report, go into sections of their own, which Sections, Pages, Headers, and Footers describes.

Build a Document From Pieces

The following handler writes a heading and a paragraph with a placeholder, appends an HTML snippet and then puts the contents of the file terms.docx where the placeholder was:

private void BuildButton_Click(object sender, RoutedEventArgs e)
{
    textControl1.ResetContents();

    // Prepare text and formatting in a Selection object, then insert it in one step
    textControl1.Selection = new Selection()
    {
        Text = "Invoice INV-2025-001\r\n",
        FontName = "Arial",
        FontSize = 480, // 24 pt
        Bold = true
    };

    textControl1.Selection = new Selection()
    {
        Text = "Terms: [TERMS]\r\n",
        FontName = "Arial",
        FontSize = 240 // 12 pt
    };

    // Append an HTML snippet in a new paragraph at the end of the document
    textControl1.Append("<p>Thank you for your <strong>order</strong>.</p>",
        StringStreamType.HTMLFormat,
        AppendSettings.StartWithNewParagraph);

    // Replace a placeholder with the contents of another document
    const string placeholder = "[TERMS]";
    int position = textControl1.Find(placeholder, 0, FindOptions.NoMessageBox);

    if (position >= 0)
    {
        textControl1.Select(position, placeholder.Length);
        textControl1.Selection.Load("terms.docx", StreamType.WordprocessingML);
    }
}

The code works as follows:

  • A new Selection object carries text and attributes. Assigning it to textControl1.Selection inserts both at the input position in one step. The blog article Speed Up Document Generation by Factor 2 Using Selection Objects in .NET C# measures how much faster this is than formatting text after inserting it, using ServerTextControl.
  • Append adds a document from a file, a byte array or a string to the end. StringStreamType.HTMLFormat reads the string as HTML.
  • Find returns the zero-based position of the text, or -1. The search starts at position 0, and FindOptions NoMessageBox suppresses the message boxes of the editor.
  • Select selects the placeholder, and Selection.Load replaces the selected text with the loaded document, or inserts it at the input position when nothing is selected.

Find searches only the text part with the input focus: while the cursor is in a header, the placeholder in the main text isn't found. Document Object Model describes why.

Append takes a value of AppendSettings as its last parameter:

ValueThe appended document starts
NoneAt the end of the last paragraph
StartWithNewParagraphIn a new paragraph at the end of the document
StartWithNewSectionIn a new section at the end of the document

Combine Several Documents

Load the first file and append the others to put a cover page and several chapters into one document. Done in a ServerTextControl, the intermediate steps stay invisible, and the window shows only the result:

private void CombineButton_Click(object sender, RoutedEventArgs e)
{
    string[] files = { "cover.docx", "chapter1.docx", "chapter2.docx" };
    byte[] document;

    using (ServerTextControl tx = new ServerTextControl())
    {
        // Initialize the new instance before using it
        if (!tx.Create())
        {
            MessageBox.Show("ServerTextControl could not be initialized.");
            return;
        }

        // The first document replaces the empty contents
        tx.Load(files[0], StreamType.WordprocessingML);

        // Every further document is appended in a new section
        for (int i = 1; i < files.Length; i++)
        {
            tx.Append(files[i], StreamType.WordprocessingML, AppendSettings.StartWithNewSection);
        }

        tx.Save(out document, BinaryStreamType.InternalUnicodeFormat);
    }

    // Show the combined document in the editor
    textControl1.Load(document, BinaryStreamType.InternalUnicodeFormat);
    MessageBox.Show($"Sections: {textControl1.Sections.Count}, pages: {textControl1.Pages}");
}

The code works as follows:

A document stored in a database is appended from its bytes, for example tx.Append(bytes, BinaryStreamType.WordprocessingML, AppendSettings.StartWithNewSection);. Loading Documents compares Load, Append and Selection.Load.

Store Document Properties

DocumentSettings holds general information about the document, such as its title, author, subject and keywords, and saving stores these values with the document. Set them before your code saves a generated document, as Set the Properties in Code in Document Properties shows, for example textControl1.DocumentSettings.DocumentTitle = "Invoice INV-2025-001";. The properties of the same name in SaveSettings override them when you save.

Build Large Documents Without Undo

Each change in the editor goes into the undo history, and hundreds of paragraphs inserted by code make that history expensive in time and memory. Set IsUndoEnabled to false while the code runs, as Switch the Buffer Off in Undo and Redo describes, and set it back to true in a finally block, so that an exception doesn't leave the editor without undo. A ServerTextControl has no undo history.

Check the Fonts of a Document

Text is formatted with the fonts available for the printer that FormattingPrinter names, and GetSupportedFonts returns their names. A font that a document uses and that isn't supported is replaced; the AdaptFont event reports each replacement and lets you choose the font, as Choose the Replacement Fonts in Font Embedding and Substitution shows. Fonts and Document Layout helps when text looks different than expected.

Next Steps