TX Text Control .NET Server

Fields

Topic

Fields mark the parts of a document that code finds and changes later: a customer name, a link to a website, a bookmark, a Word merge field. On the server, you insert these fields, find them in the main text, headers, footers and text frames, change and remove them and turn them into plain text before a document leaves your application.

Merge fields that MailMerge fills with data are covered in Merge Fields, and the content controls of MS Word forms in Content Controls. Every program below replaces the content of Program.cs in a console application.

The Kinds of Fields

A field is a marked piece of text with properties such as a name. Each kind of field has a collection of its own:

FieldCollection of ServerTextControlUsed for
TextFieldTextFieldsText that your code identifies by its name or ID
ApplicationFieldApplicationFieldsFields of MS Word, such as MERGEFIELD, IF and DATE
HypertextLinkHypertextLinksLinks to a website or another document
DocumentTargetDocumentTargetsBookmarks, positions that links point to
DocumentLinkDocumentLinksLinks to a bookmark in the same document
PageNumberFieldHeaderFooter.PageNumberFieldsPage numbers in headers and footers, described in Headers and Footers
FormFieldFormFieldsForm fields that users fill out, described in Form Fields

Keep these points in mind:

  • Main text only. The collections of ServerTextControl contain the fields of the main text. Headers, footers and text frames have the same collections for their own fields.
  • Word fields need a load setting. Fields of MS Word documents are imported as application fields only when LoadSettings.ApplicationFieldFormat is set before loading, as Load a Document shows.
  • Several kinds at once. GetTextFields returns the fields of the kinds you combine from TextFieldType, such as TextFieldType.HypertextLink | TextFieldType.DocumentLink.

Insert a Text Field

A text field is inserted at the current input position. The following program writes a label and inserts a field with the customer name behind it:

using TXTextControl;

using (ServerTextControl tx = new ServerTextControl())
{
    tx.Create();

    tx.Selection.Text = "Customer: ";

    // A name and an ID let your code find the field again
    TextField customerField = new TextField("Acme Corporation")
    {
        Name = "CustomerName",
        ID = 1
    };

    if (tx.TextFields.CanAdd && tx.TextFields.Add(customerField))
    {
        Console.WriteLine($"Field inserted at position {customerField.Start}");
    }

    tx.Save("fields.tx", StreamType.InternalUnicodeFormat);
}

The code works as follows:

  • The TextField constructor takes the text of the field.
  • Name is any text, and ID a number. Both are saved with the document and identify the field later.
  • CanAdd tells whether a field can be inserted at the input position; a field can't be inserted into another field. Add returns true if the field was inserted.
  • Start is the one-based position of the field's first character.
  • Text fields are kept in the TX Text Control format, so the document is saved as fields.tx.

Name has no length limit, so a field can carry a larger piece of data, such as a JSON string, along with its text.

Set How Users Edit the Field

The following properties are saved with the document and take effect when a user edits it later, for example in the Document Editor:

  • Editable decides whether users can change the text of the field, and Deleteable whether they can delete it. A field that isn't deleteable keeps its text editable, but a selection with the field inside can't be deleted or replaced.
  • DoubledInputPosition adds a second input position in front of the first and behind the last character of the field, so that users decide whether text typed at the edge belongs to the field. It's false by default and always true for form fields.
  • HighlightMode decides when the field is shown with a colored background. With HighlightMode.Activated, the field is highlighted while the input position is inside it, where the next typed character becomes part of the field. HighlightColor sets the color; its default is black with an alpha value of 60, a light gray.
TextField priceField = new TextField("$ 22.45")
{
    Name = "Price",
    DoubledInputPosition = true,
    HighlightMode = HighlightMode.Activated
};

tx.TextFields.Add(priceField);

Find the Fields of All Text Parts

To find every field of a document, including those in headers, footers and text frames, loop through ServerTextControl.TextParts. Each text part implements IFormattedText, which has the field collections:

using TXTextControl;

LoadSettings loadSettings = new LoadSettings()
{
    ApplicationFieldFormat = ApplicationFieldFormat.MSWord
};

using (ServerTextControl tx = new ServerTextControl())
{
    tx.Create();
    tx.Load("Documents/sample.docx", StreamType.WordprocessingML, loadSettings);

    foreach (IFormattedText textPart in tx.TextParts)
    {
        // MainText, TextFrame, HeaderFooter or FootnoteText
        string part = textPart.GetType().Name;

        foreach (ApplicationField field in textPart.ApplicationFields)
        {
            string parameters = field.Parameters != null ? string.Join(" ", field.Parameters) : "";
            Console.WriteLine($"{part}: {field.TypeName} {parameters} = \"{field.Text}\"");
        }

        foreach (HypertextLink link in textPart.HypertextLinks)
        {
            Console.WriteLine($"{part}: link to {link.Target} = \"{link.Text}\"");
        }

        foreach (DocumentTarget target in textPart.DocumentTargets)
        {
            Console.WriteLine($"{part}: bookmark {target.TargetName}");
        }
    }
}

The text parts come in a fixed order: the main text first, then the text frames and then the headers and footers. For an application field, TypeName is the Word field type, such as MERGEFIELD, and Parameters holds the rest of the field code: for MERGEFIELD Address \* Upper \v, the three elements Address, \* Upper and \v.

Field Formats and Type Names

The field formats of different applications aren't compatible, so LoadSettings.ApplicationFieldFormat names the one to import. ApplicationFieldFormat has these values:

ValueImports
NoneNo application fields
MSWordThe fields of MS Word, including its legacy form fields and content controls
MSWordTXFormFieldsThe form fields of MS Word as TX Text Control form fields, the other Word fields as application fields, described in Content Controls
HighEditThe fields of the Heiler HighEdit component in RTF documents

LoadSettings.ApplicationFieldTypeNames limits the import to the type names you list, such as new string[] { "MERGEFIELD", "PRINTDATE" }. Besides the field names of MS Word, two groups of type names hold their settings as Office Open XML markup in Parameters rather than as switches:

Type nameWord elementWhat Parameters holds
FORMCHECKBOX, FORMDROPDOWN, FORMTEXTThe legacy form fields: check box, drop-down list and text boxThe options of Word's Form Field Options dialog box
SDTBLOCKA content controlIts properties; the w:val attribute of the w:tag element names the kind of control: rich text, plain text, picture, combo box, drop-down list or check box

The FormText, FormCheckBox and FormDropDown adapters and the content control adapters in Use Field Adapters for MS Word Fields read this markup for you.

Change and Remove Fields

The collections have the same methods for all kinds of fields. GetItem with an ID returns a field or null, and Remove removes a field, with or without its text. A collection can't be changed while a foreach loop runs over it, so collect the fields to remove first:

using TXTextControl;

using (ServerTextControl tx = new ServerTextControl())
{
    tx.Create();
    tx.Load("fields.tx", StreamType.InternalUnicodeFormat);

    // Change the text of a field by its name
    foreach (TextField field in tx.TextFields)
    {
        if (field.Name == "CustomerName")
        {
            field.Text = "Text Control, LLC";
        }
    }

    // Remove the field with the ID 1 and keep its text
    TextField fieldWithId = tx.TextFields.GetItem(1);

    if (fieldWithId != null)
    {
        tx.TextFields.Remove(fieldWithId, true);
    }

    // Collect the merge fields first, then remove them with their text
    List<ApplicationField> mergeFields = new List<ApplicationField>();

    foreach (ApplicationField field in tx.ApplicationFields)
    {
        if (field.TypeName == "MERGEFIELD")
        {
            mergeFields.Add(field);
        }
    }

    foreach (ApplicationField field in mergeFields)
    {
        tx.ApplicationFields.Remove(field, false);
    }

    tx.Save("fields-changed.tx", StreamType.InternalUnicodeFormat);
}

TextFieldCollection.GetItem without a parameter returns the field at the input position. Remove with true as second parameter removes the field and keeps its text; without it, or with false, the text is removed too.

Change the Parameters of a Word Field

Parameters returns a .NET array, which has a fixed size. To add or remove a switch, build a new array and assign it to the property. The following program removes the \v switch from every merge field and sets the text the Address field shows:

using TXTextControl;

LoadSettings loadSettings = new LoadSettings()
{
    ApplicationFieldFormat = ApplicationFieldFormat.MSWord
};

using (ServerTextControl tx = new ServerTextControl())
{
    tx.Create();
    tx.Load("Documents/sample.docx", StreamType.WordprocessingML, loadSettings);

    foreach (ApplicationField field in tx.ApplicationFields)
    {
        if (field.TypeName != "MERGEFIELD" || field.Parameters == null)
        {
            continue;
        }

        // A new array without the \v switch replaces the old one
        if (Array.IndexOf(field.Parameters, "\\v") >= 0)
        {
            field.Parameters = field.Parameters.Where(p => p != "\\v").ToArray();
        }

        // The first parameter of a MERGEFIELD is the field name
        if (field.Parameters.Length > 0 && field.Parameters[0] == "Address")
        {
            field.Text = "221B Baker Street, London";
        }
    }

    tx.Save("sample-fields.docx", StreamType.WordprocessingML);
}

The code works as follows:

  • "\\v" in C# is the two characters \v of the Word switch. A single backslash would be the vertical tab character.
  • Where and ToArray create a new array; assigning it to Parameters changes the field code.
  • Setting Text changes what the field shows, not its type or parameters.

React to Created and Deleted Fields

TextFieldCreated and TextFieldDeleted occur for fields of every class, and TextFieldEventArgs.TextField holds the field:

  • Created. Names and IDs are saved and copied with a field, so a copy has the same name as the original. The handler of TextFieldCreated is the place to give a new field a name or ID of its own, such as "Copy of Price" for a second "Price" field.
  • Deleted. After a field has been deleted, only its ID and Name are valid. Identify the removed field by them and don't read its text or position.
tx.TextFieldDeleted += (sender, e) =>
{
    // Only ID and Name of a deleted field are valid
    Console.WriteLine($"Field removed: {e.TextField.Name} ({e.TextField.ID})");
};

Events lists these events with the other events of ServerTextControl.

Show Field Codes

MS Word can show a field as its field code, such as { MERGEFIELD Total \# "#,##0.00" }, instead of its text. TX Text Control has no switch for this, but the code can be built from the field's TypeName and Parameters and set as the field's text. A proof copy of a template that shows every field code helps authors check field names and formats. The following program shows the codes of the merge fields in a small template, saves the proof as PDF, puts the original texts back and saves the template:

using TXTextControl;
using TXTextControl.DocumentServer.Fields;

using (ServerTextControl tx = new ServerTextControl())
{
    tx.Create();

    // A template with two merge fields
    tx.Selection.Text = "Dear ";
    MergeField name = new MergeField() { Name = "CustomerName", Text = "«CustomerName»" };
    tx.ApplicationFields.Add(name.ApplicationField);

    tx.Selection.Text = ",\r\nyour order total is ";
    MergeField total = new MergeField() { Name = "Total", Text = "«Total»", NumericFormat = "#,##0.00", TextAfter = " USD" };
    tx.ApplicationFields.Add(total.ApplicationField);
    tx.Selection.Text = ".";

    // Show the field codes and remember the field texts
    List<(ApplicationField Field, string Text)> fieldTexts = new List<(ApplicationField, string)>();

    foreach (ApplicationField field in tx.ApplicationFields)
    {
        fieldTexts.Add((field, field.Text));
        field.Text = "{ " + field.TypeName + " " + string.Join(" ", field.Parameters) + " }";
    }

    tx.Save("template-field-codes.pdf", StreamType.AdobePDF);

    // Show the field texts again
    foreach ((ApplicationField field, string text) in fieldTexts)
    {
        field.Text = text;
    }

    tx.Save("template.docx", StreamType.WordprocessingML);
}

The PDF shows the two lines as Dear { MERGEFIELD CustomerName }, and your order total is { MERGEFIELD Total \f " USD" \# "#,##0.00" }.: the MergeField adapter stores the text after the field and the number format as the switches \f and \#. Changing TextField.Text changes only what the field shows; its type and parameters stay, so the restored template still contains both merge fields. For templates loaded from DOCX files, set ApplicationFieldFormat as in the section above. The blog article Toggle Field Codes in TX Text Control toggles the codes of a single field, also in the Document Editor.

Convert Fields to Plain Text

Before a document is sent to a customer, its fields can be removed while their visible text stays. Clear(true) does this for all fields of a collection:

using TXTextControl;

LoadSettings loadSettings = new LoadSettings()
{
    ApplicationFieldFormat = ApplicationFieldFormat.MSWord
};

using (ServerTextControl tx = new ServerTextControl())
{
    tx.Create();
    tx.Load("Documents/sample.docx", StreamType.WordprocessingML, loadSettings);

    foreach (IFormattedText textPart in tx.TextParts)
    {
        // true removes the fields and keeps their text
        textPart.ApplicationFields.Clear(true);
        textPart.TextFields.Clear(true);
        textPart.HypertextLinks.Clear(true);
        textPart.DocumentLinks.Clear(true);

        // Bookmarks have no text of their own
        textPart.DocumentTargets.Clear();
    }

    tx.Save("sample-plain.docx", StreamType.WordprocessingML);
}

ApplicationFieldCollection.Clear and the Clear methods of the other collections take keepText as parameter. DocumentTargetCollection.Clear takes none.

Use Field Adapters for MS Word Fields

An ApplicationField only gives access to the type name, parameters and text of a Word field; the logic of the field isn't implemented. The classes of the TXTextControl.DocumentServer.Fields namespace are adapters for typical Word fields: they wrap an application field and give typed access to its settings, so you don't have to know the syntax of the field code.

  • Change an existing field: pass the application field to the adapter's constructor, for example new MergeField(applicationField).
  • Create a new field: create the adapter, set its properties and add its ApplicationField to a collection, for example tx.ApplicationFields.Add(mergeField.ApplicationField).
AdapterFieldDescribed in
MergeFieldMERGEFIELDMerge Fields
DateFieldDATE, the current date in a formatMerge Fields
IfFieldIF, text that depends on a comparisonConditional Content
IncludeTextINCLUDETEXT, another document included in a templateTemplate Design
NextField, NextIfFieldNEXT and NEXTIF, which advance to the next data recordOverview, Conditional Content
FormText, FormCheckBox, FormDropDownThe legacy form fields of MS Word—
ContentControlFieldAdapter and its derived classesThe content controls of MS WordContent Controls

All adapters derive from FieldAdapter, whose ApplicationField property returns the wrapped field. The blog article Find ApplicationFields within Sections and Paragraphs in .NET C# finds the fields of a particular section or paragraph by comparing positions.

Next Steps