Creating Documents
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.Selectioninserts 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, usingServerTextControl. - Append adds a document from a file, a byte array or a string to the end.
StringStreamType.HTMLFormatreads the string as HTML. - Find returns the zero-based position of the text, or -1. The search starts at position 0, and FindOptions
NoMessageBoxsuppresses 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:
| Value | The appended document starts |
|---|---|
None | At the end of the last paragraph |
StartWithNewParagraph | In a new paragraph at the end of the document |
StartWithNewSection | In 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:
- ServerTextControl.Load replaces the empty contents with the first file. ServerTextControl.Append adds every other file in a new section, which keeps its page format.
- ServerTextControl.Save writes the result to a byte array in the TX Text Control format, and Load of the
TextControlshows it.
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
- Loading Documents: load existing documents with all their options.
- Text, Paragraphs, and Stylesheets: format the text of the new document.
- Saving and Converting Documents: save the finished document in the format you need.
- Sections, Pages, Headers, and Footers: give parts of a document their own page format.