TX Text Control .NET for WPF

Fields and Subtext Parts

Topic

How does an application find the customer name in a letter again, open the link a user clicks, or know that the cursor is inside the payment clause of a contract? Fields and subtext parts answer these questions. Fields mark short pieces of text such as names, links, bookmarks and MS Word fields; subtext parts mark longer ranges and can carry data. In a loaded document, your code collects the fields of every text part, reads and changes MS Word fields with typed field adapters and keeps data with ranges of text.

The code goes into MainWindow.xaml.cs of a window whose TextControl is named textControl1 and which shows results in a TextBlock named statusText. Merge fields filled with data are described in Merge Fields, fillable form fields in Forms and Conditional Instructions and page numbers in Sections, Pages, Headers, and Footers.

MainWindow.xaml.cs needs these using directives:

using System.Text;
using System.Windows;
using TXTextControl;
using TXTextControl.DocumentServer.Fields;

The Kinds of Fields

Every field is a marked piece of text with properties such as a name and an ID, and each kind is kept in its own collection of WPF.TextControl: TextFields, ApplicationFields for MS Word fields, HypertextLinks, DocumentTargets for bookmarks and DocumentLinks. Fields, Hyperlinks, and Bookmarks explains each kind and shows how to insert, fill, find and remove text fields, insert links and bookmarks, handle the field events and handle clicks on links. Keep in mind:

  • Only the active text part. The collections contain the fields of the text part with the input focus, which is the main text, a text frame, or a header or footer. TextParts gives you every part, as the next section shows.
  • MS Word fields are imported on request. Set LoadSettings.ApplicationFieldFormat before loading a Word document, or ApplicationFields stays empty.
  • Names and IDs of text fields are kept in the TX Text Control format, StreamType.InternalUnicodeFormat.
  • Links don't navigate by themselves. Open only web addresses from a HypertextLinkClicked handler, so that documents can't start programs; see Handling Untrusted Documents.

Find the Fields of All Text Parts

TextParts contains the main text, the text frames and the headers and footers, each as an IFormattedText with its own field collections. This handler loads a Word template with its fields and lists the merge fields and links of every part:

private void ListFields_Click(object sender, RoutedEventArgs e)
{
    LoadSettings loadSettings = new LoadSettings()
    {
        ApplicationFieldFormat = ApplicationFieldFormat.MSWord
    };

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

    StringBuilder list = new StringBuilder();

    foreach (IFormattedText textPart in textControl1.TextParts)
    {
        foreach (ApplicationField field in textPart.ApplicationFields)
        {
            list.AppendLine($"{textPart.GetType().Name}: {field.TypeName} = \"{field.Text}\"");
        }

        foreach (HypertextLink link in textPart.HypertextLinks)
        {
            list.AppendLine($"{textPart.GetType().Name}: link to {link.Target}");
        }
    }

    statusText.Text = list.ToString();
}

TypeName is the type of a Word field, and Parameters holds the rest of its field code. The field adapters in Use Field Adapters for MS Word Fields read and write these settings with typed properties.

ApplicationFieldCollection.Clear with true removes all fields of a collection and keeps their text, which turns them into plain text. Remove(field, true) does this for one field.

Use Field Adapters for MS Word Fields

An ApplicationField gives you the type name and the raw parameters of a Word field, but it doesn't know what they mean. The classes of the TXTextControl.DocumentServer.Fields namespace are adapters for the common Word fields: an adapter wraps an application field and offers its settings as typed properties, so your code doesn't have to parse or build field codes.

  • Change an existing field: pass the application field to the adapter's constructor, for example new MergeField(field). Changes to the adapter's properties change the field in the document.
  • Create a new field: create the adapter without parameters, set its properties and add its ApplicationField with textControl1.ApplicationFields.Add.

Every adapter class has the constant TYPE_NAME, the type name of the Word field it handles. Comparing it with TypeName tells you which adapter fits a field:

AdapterWord fieldDescribed in
MergeFieldMERGEFIELD, a value from the dataMerge Fields
DateFieldThe current date in a formatMerge Fields
IfFieldIF, text that depends on a comparisonConditional Content
IncludeTextINCLUDETEXT, another document included in a templateSubtemplates
NextField, NextIfFieldNEXT and NEXTIF, which continue with the next data record, always or on a conditionOverview of Mail Merge and Reporting
FormText, FormCheckBox, FormDropDownThe legacy form fields of MS WordContent Controls
ContentControlFieldAdapter and its six derived classesThe content controls of MS WordContent Controls

The first six derive from MailMergeFieldAdapter, the legacy form fields from FormFieldAdapter, and all of them from FieldAdapter. The following handler shows the settings of the Word field at the input position:

private void ShowFieldSettings_Click(object sender, RoutedEventArgs e)
{
    // The application field at the input position, or null
    ApplicationField field = textControl1.ApplicationFields.GetItem();

    if (field == null)
    {
        statusText.Text = "No MS Word field at the input position";
    }
    else if (field.TypeName == MergeField.TYPE_NAME)
    {
        MergeField mergeField = new MergeField(field);
        statusText.Text = $"Merge field {mergeField.Name}, text before: \"{mergeField.TextBefore}\"";
    }
    else if (field.TypeName == IfField.TYPE_NAME)
    {
        IfField ifField = new IfField(field);
        statusText.Text = $"IF {ifField.Expression1} {ifField.Operator} {ifField.Expression2}";
    }
    else if (field.TypeName == NextIfField.TYPE_NAME)
    {
        NextIfField nextIfField = new NextIfField(field);
        statusText.Text = $"NEXTIF {nextIfField.Expression1} {nextIfField.Operator} {nextIfField.Expression2}";
    }
    else
    {
        statusText.Text = $"{field.TypeName} field: {string.Join(" ", field.Parameters)}";
    }
}

The code works as follows:

FieldAdapter.ShowDialog opens the options dialog box of a field; in a WPF application, pass the Window as its owner, for example mergeField.ShowDialog(this). Reporting Ribbon and WPF Dialog Boxes opens it for the field at the input position.

Mark Ranges With Subtext Parts

A SubTextPart covers a continuous range of text, tables and fields included, and stores a name, an ID and a Data string. Subtext parts nest but never overlap, and Merge Blocks are built on them. Subtext Parts shows how to add, list, find, save and remove them and how to handle their events. The Data string survives only in the TX Text Control format; DOCX, DOC and RTF files keep the ranges, which come back as subtext parts when LoadSettings.LoadSubTextParts is true. The blog article The Power of SubTextParts: Typical Use Cases collects further uses; its code is written for the Windows Forms TextControl.

Next Steps