TX Text Control .NET for WPF

Loading Documents

Topic

Whether a WPF application opens a contract the user picked, a template stored in a database or a PDF attached to an email, it loads the document into the TextControl of its window or into a ServerTextControl. Beyond the basic Load call, you choose whether a document replaces, extends or goes into the current one, control what is imported, read the properties of the loaded file and react to documents that can't be loaded.

For the Open dialog box and a first set of load and save buttons, see Load and Save Documents and Loading and Saving Documents. Supported File Formats lists the formats and their StreamType values. The examples are Click handlers in MainWindow.xaml.cs, which needs using System.Windows; and using TXTextControl;, and work on the TextControl named textControl1. A ServerTextControl has the same methods.

Load, Append or Insert

The three methods that load a document accept the same sources and formats and an optional LoadSettings object. They differ in what they do with the current contents:

MethodWhat happens to the current contents
TextControl.LoadThey're replaced completely
TextControl.AppendThey stay; the document is added at the end, as an AppendSettings value sets
Selection.LoadThe selected text is replaced; with nothing selected, the document is inserted at the input position

The following handler loads a letter, appends a signature and puts an attachment where a placeholder was:

private void LoadLetterButton_Click(object sender, RoutedEventArgs e)
{
    // Load replaces the complete contents
    textControl1.Load("letter.docx", StreamType.WordprocessingML);

    // Append adds a document at the end, here in a new paragraph
    textControl1.Append("signature.rtf", StreamType.RichTextFormat,
        AppendSettings.StartWithNewParagraph);

    // Selection.Load replaces the selected text, here a placeholder
    const string placeholder = "[ATTACHMENT]";
    int position = textControl1.Find(placeholder, 0, FindOptions.NoMessageBox);

    if (position >= 0)
    {
        textControl1.Select(position, placeholder.Length);

        LoadSettings loadSettings = new LoadSettings()
        {
            // Create a new paragraph before the document is loaded
            AddParagraph = true
        };

        textControl1.Selection.Load("attachment.docx", StreamType.WordprocessingML, loadSettings);
    }
}

Only Selection.Load uses LoadSettings.AddParagraph. By default, the document goes into the existing paragraph, whose attributes are merged with those of the last loaded paragraph. With true, a new paragraph is created first, and it takes the attributes of the last loaded paragraph. The AppendSettings values are listed in Creating Documents.

Control the Import With LoadSettings

Set these properties of LoadSettings before you load, and they decide what is imported:

PropertySetsDefault
ApplicationFieldFormatWhether fields of Word and RTF documents are imported as application fields, as a value of ApplicationFieldFormatNone
ApplicationFieldTypeNamesThe field types to import, such as MERGEFIELD, when ApplicationFieldFormat is MSWordAll types
LoadSubTextPartsWhether bookmarks that extend over several characters become subtext parts, for DOCX, DOC and RTFfalse
LoadImagesWhether images are loaded; otherwise a rectangle with the image's name is showntrue
LoadHypertextLinksWhether hypertext links are loadedtrue
LoadDocumentBackColorWhether the background color of the document is loadedtrue
ImageSearchPathA folder that replaces the absolute paths of linked images and other resources—
DocumentBasePathA base path that resolves the relative file paths of the document—
PDFImportSettingsHow the structure of a PDF document is rebuiltGenerateTextFrames
DocumentPartNameThe worksheet of an XLSX file; an empty string lets the user choose it in a dialog boxThe first worksheet
UserPassword, MasterPasswordThe passwords of an encrypted PDF document—

The following handler loads a Word template with only its merge fields and its merge blocks, leaving out hypertext links and the background color:

private void LoadTemplateButton_Click(object sender, RoutedEventArgs e)
{
    LoadSettings loadSettings = new LoadSettings()
    {
        ApplicationFieldFormat = ApplicationFieldFormat.MSWord,
        ApplicationFieldTypeNames = new string[] { "MERGEFIELD" }, // only merge fields
        LoadSubTextParts = true,        // bookmarks over several characters become subtext parts
        LoadHypertextLinks = false,
        LoadDocumentBackColor = false
    };

    textControl1.Load("template.docx", StreamType.WordprocessingML, loadSettings);

    MessageBox.Show($"Merge fields: {textControl1.ApplicationFields.Count}, " +
                    $"subtext parts: {textControl1.SubTextParts.Count}");
}

ApplicationFieldFormat takes one of four values, and it applies to Word and RTF documents only:

ValueImports
NoneNo application fields; Word form fields become form fields of TX Text Control
MSWordThe fields of Microsoft Word as application fields, form fields and content controls included
MSWordTXFormFieldsThe fields of Microsoft Word as application fields, except form fields, which become form fields of TX Text Control
HighEditThe fields of the Heiler HighEdit component as application fields

When ApplicationFieldFormat isn't set, Word fields aren't imported as fields, and ApplicationFields stays empty. The merge blocks of a mail merge template are subtext parts whose names begin with txmb_; with LoadSubTextParts set to true, they are back in SubTextParts after loading. Fields and Subtext Parts describes both collections, and PDF Import and Its Limitations the values of PDFImportSettings.

Read Information About a Loaded Document

The LoadSettings object you pass is filled with information about the document during the load, also when the user picks the file in the Open dialog box, which Open and Save With the Built-In Dialog Boxes describes. The following handler opens the dialog box and shows what the LoadSettings object contains afterward:

private void OpenButton_Click(object sender, RoutedEventArgs e)
{
    LoadSettings loadSettings = new LoadSettings();

    // Opens the Open dialog box with the filters of these formats
    TXTextControl.WPF.DialogResult result = textControl1.Load(
        StreamType.WordprocessingML | StreamType.RichTextFormat | StreamType.AdobePDF,
        loadSettings);

    if (result != TXTextControl.WPF.DialogResult.OK)
    {
        return;
    }

    StringBuilder info = new StringBuilder();
    info.AppendLine($"File: {loadSettings.LoadedFile}");
    info.AppendLine($"Format: {loadSettings.LoadedStreamType}, {loadSettings.BytesRead} bytes");
    info.AppendLine($"Title: {loadSettings.DocumentTitle}");
    info.AppendLine($"Author: {loadSettings.Author}");
    info.AppendLine($"Last modified: {loadSettings.LastModificationDate}");

    if (loadSettings.UserDefinedDocumentProperties != null)
    {
        foreach (string name in loadSettings.UserDefinedDocumentProperties.Names)
        {
            info.AppendLine($"{name}: {loadSettings.UserDefinedDocumentProperties[name]}");
        }
    }

    MessageBox.Show(info.ToString());
}

The handler also needs using System.Text;. The information properties are read only:

PropertyContains
LoadedFile, LoadedStreamType, BytesReadThe file with its path, the format that was loaded and the number of bytes read
DocumentTitle, Author, DocumentSubject, DocumentKeywords, CreatorApplicationThe metadata of the document
CreationDate, LastModificationDateThe dates of the document; January 1, 0001 if the document has none
UserDefinedDocumentPropertiesA UserDefinedPropertyDictionary with the user-defined properties, or null
PageSize, PageMarginsThe page size, 0 if the document has none, and the page margins, -1 if the document has none
DocumentAccessPermissionsWhat may be done with a loaded PDF document

The keys of the dictionary are in its Names, and its values are strings, Boolean values, integers or double values. The same metadata goes into DocumentSettings of the TextControl and is saved with the document again.

Load From Byte Arrays, Strings and Streams

A document stored in a database comes as a byte array, and HTML or RTF often as a string. Use a BinaryStreamType for bytes and a StringStreamType for a string:

private void LoadFromDatabaseButton_Click(object sender, RoutedEventArgs e)
{
    // For example the contents of a database column
    byte[] data = File.ReadAllBytes("invoice.docx");
    textControl1.Load(data, BinaryStreamType.WordprocessingML);

    // HTML and RTF are loaded from a string
    string html = "<h1>Terms</h1><p>Payment is due within 14 days.</p>";
    textControl1.Append(html, StringStreamType.HTMLFormat, AppendSettings.StartWithNewSection);
}

This handler needs using System.IO;. BinaryStreamType covers DOCX, DOC, PDF, XLSX and the TX Text Control formats; HTML and RTF come from a string or a file. The only stream Load accepts is a FileStream, so a MemoryStream or a stream from a web request is loaded through its bytes, stream.ToArray().

Load Password-Protected PDF Documents

Without its user password, a protected PDF document can't be opened, and Load throws a FilterException with the Reason InvalidPassword. Your application asks the user for the password and loads the document again with it in UserPassword of the LoadSettings; afterward, DocumentAccessPermissions says what the document allows. Open a Password-Protected PDF in Passwords and Access Permissions shows the code with a password prompt.

Handle Errors and the DocumentLoaded Event

A document that can't be imported makes Load throw a FilterException, whose Reason is a value of FilterException.FilterError, such as WrongFileType or InvalidPassword. Catch Errors When Opening a Document in Exceptions and Error Handling shows the try block, and Read the Reason of a FilterException in Troubleshooting lists every reason with its meaning. AdobePDFA and CascadingStylesheet can only be saved, not loaded.

When a document has been loaded, the TextControl raises DocumentLoaded. Connect it in XAML and update the window there:

<WPF:TextControl x:Name="textControl1" DocumentLoaded="TextControl1_DocumentLoaded" />
private void TextControl1_DocumentLoaded(object sender, System.EventArgs e)
{
    Title = $"{textControl1.Pages} pages";
}

DocumentLoaded isn't the WPF Loaded event, which occurs once when the control is added to the window.

Next Steps