Fields, Hyperlinks, and Bookmarks
If your application needs to find a value in a document again, replace it later or link it to a website or another page, it marks that text as a field. The WPF TextControl supports general text fields, MS Word fields such as merge fields, hyperlinks, bookmarks and links to bookmarks, and it tells your code through events when the user clicks or changes one.
The examples use a window with a TextControl named textControl1, as in Create Your First WPF Application. The handlers belong in MainWindow.xaml.cs, which needs using TXTextControl; and using System.Windows;.
Kinds of Fields
All fields are based on TextField: a marked piece of text with a name, an ID and events. The WPF TextControl has one collection per kind of field:
| Class | Represents | Collection |
|---|---|---|
| TextField | A general field, for example for a value from a database | WPF.TextControl.TextFields |
| ApplicationField | A field of MS Word, such as MERGEFIELD or DATE | WPF.TextControl.ApplicationFields |
| HypertextLink | A link to a URL or a file outside the document | WPF.TextControl.HypertextLinks |
| DocumentTarget | A bookmark, a named position in the document | WPF.TextControl.DocumentTargets |
| DocumentLink | A link to a bookmark of the same document | WPF.TextControl.DocumentLinks |
Each collection contains the fields of the text part with the input focus, which is the main text, a header or footer, or a text frame. Form fields are covered on Form Fields and Conditional Instructions, and page numbers, which are PageNumberField objects in headers and footers, on Headers and Footers.
Insert and Fill Text Fields
private void InsertField_Click(object sender, RoutedEventArgs e)
{
TextField customerField = new TextField("Customer name")
{
Name = "CustomerName",
ID = 100,
HighlightMode = HighlightMode.Activated,
// Let users type in front of and behind the field
DoubledInputPosition = true
};
if (textControl1.TextFields.CanAdd)
{
textControl1.TextFields.Add(customerField);
}
}
private void FillFields_Click(object sender, RoutedEventArgs e)
{
foreach (TextField field in textControl1.TextFields)
{
if (field.Name == "CustomerName")
{
field.Text = "Paul Paulsen";
}
}
}
Connect the handlers to two buttons, for example <Button Content="Insert Field" Click="InsertField_Click" /> next to the TextControl in MainWindow.xaml.
- The TextField constructor takes the visible text, which Text reads and replaces later.
- Name and ID are values of your own that identify the field. They are saved with the document and also copied when the user copies the field through the clipboard.
Nametakes any string, and its length isn't limited, so a field can carry a large amount of data of your own. - HighlightMode gives the field a gray background:
Activatedwhile the input position is in it,Always, orNever, the default. - DoubledInputPosition creates two input positions at each end of the field, one inside and one outside, which users reach with the arrow keys.
- CanAdd is
falseat positions where no field can be inserted, such as inside another field. TextFieldCollection.Add inserts the field at the input position.
Set Editable to false to keep users from changing the text of a field, and Deleteable to false to keep them from deleting it. FieldCursor sets the mouse cursor over fields.
Find and Remove Fields
private void RemoveField_Click(object sender, RoutedEventArgs e)
{
TextField field = textControl1.TextFields.GetItem(100);
if (field != null)
{
// true: keep the text of the field in the document
textControl1.TextFields.Remove(field, true);
}
}
private void ListAllFields_Click(object sender, RoutedEventArgs e)
{
StringBuilder list = new StringBuilder();
foreach (TextField field in textControl1.GetTextFields(TextFieldType.All))
{
list.AppendLine(field.GetType().Name + " " + field.Name + ": " + field.Text);
}
MessageBox.Show(this, list.ToString());
}
StringBuilder needs using System.Text;.
- GetItem returns the field at the input position, or the one with an ID. Remove removes it; with
true, its text stays in the document. - WPF.TextControl.GetTextFields collects fields of several kinds at once, selected with TextFieldType:
TextField,ApplicationField,HypertextLink,DocumentLink,FormFields,Alland others.
Text Field Events
The WPF TextControl raises the following events. Their handlers receive a TextFieldEventArgs, and its TextField property is the field:
| Event | Occurs when |
|---|---|
| TextFieldCreated, TextFieldDeleted | A field is inserted or deleted, also through the clipboard, undo or redo |
| TextFieldChanged | The text of a field has changed, but not its formatting |
| TextFieldClicked, TextFieldDoubleClicked | A field is clicked or double-clicked |
| TextFieldEntered, TextFieldLeft | The input position moves into or out of a field |
Connect a handler as an attribute of the control, TextFieldClicked="textControl1_TextFieldClicked". This one shows the name of the clicked field in the title bar of the window:
private void textControl1_TextFieldClicked(object sender, TextFieldEventArgs e)
{
Title = "Clicked: " + e.TextField.Name;
}
A field pasted from the clipboard keeps the name and ID of the original. Rename it in a TextFieldCreated handler if your code expects unique names.
In a TextFieldDeleted handler, the field is no longer part of the document: of the TextField in the event arguments, only ID and Name hold valid values. To find out which field was deleted, compare these two.
TextFieldDoubleClicked is raised for every field whose DoubleClickEvent property is true, the default. While the event is raised, a double-click on the field doesn't select a word as it does in other text. Set DoubleClickEvent to false for a field to get the normal word selection back and no event.
Load MS Word Fields
ApplicationField objects represent the fields of MS Word documents. They are created when a document is loaded with LoadSettings.ApplicationFieldFormat set to MSWord:
private void LoadWordFields_Click(object sender, RoutedEventArgs e)
{
LoadSettings loadSettings = new LoadSettings()
{
// Load MS Word fields as ApplicationField objects
ApplicationFieldFormat = ApplicationFieldFormat.MSWord,
ApplicationFieldTypeNames = new string[] { "MERGEFIELD", "DATE" }
};
textControl1.Load("template.docx", StreamType.WordprocessingML, loadSettings);
StringBuilder list = new StringBuilder();
foreach (ApplicationField field in textControl1.ApplicationFields)
{
// For a MERGEFIELD, the first parameter is the field name
string[] parameters = field.Parameters ?? new string[0];
list.AppendLine(field.TypeName + " " + string.Join(" ", parameters));
}
MessageBox.Show(this, list.ToString());
}
- ApplicationFieldTypeNames restricts the import to the listed field types. When it isn't set, all MS Word fields are loaded.
- TypeName is the type of the field, and Parameters are the field name and the switches: for
MERGEFIELD Address \* Upper, the first element isAddress.
Change the Text and the Parameters
The visible text of an application field is its Text, as for every field. Parameters is a .NET array, and .NET arrays have a fixed size: to add or remove a switch, build a new array and assign it to Parameters. The following handler shows a value in the MERGEFIELD named Address and removes the \v switch from all fields that have it:
private void ChangeWordFields_Click(object sender, RoutedEventArgs e)
{
foreach (ApplicationField field in textControl1.ApplicationFields)
{
string[] parameters = field.Parameters;
if (parameters == null)
{
continue;
}
if (field.TypeName == "MERGEFIELD" && parameters[0] == "Address")
{
field.Text = "Bakerstreet, London";
}
// Copy all parameters except the \v switch into a new array
int index = Array.IndexOf(parameters, "\\v");
if (index >= 0)
{
string[] newParameters = new string[parameters.Length - 1];
Array.Copy(parameters, newParameters, index);
Array.Copy(parameters, index + 1, newParameters, index, parameters.Length - index - 1);
field.Parameters = newParameters;
}
}
}
Array needs using System;. A field without parameters has null as Parameters, which the handler skips. Filling merge fields with data is the job of the MailMerge class and of the Reporting tab: see Merge Fields.
Word Form Fields and Content Controls
With ApplicationFieldFormat.MSWord, the legacy form fields and the content controls of a Word document are imported as application fields too, with their settings as Office Open XML markup in Parameters. Content Controls lists their type names, shows which ApplicationFieldFormat turns them into which fields and reads and changes them with field adapters.
To edit Word form fields with the form fields of TX Text Control instead, load the document with ApplicationFieldFormat.MSWordTXFormFields, or with the default None: see Form Fields and Conditional Instructions. Loading Documents lists all values of ApplicationFieldFormat, including HighEdit for the fields of the Heiler HighEdit component.
Hyperlinks and Bookmarks in the Ribbon
The Insert tab of the WPF ribbon, WPF.RibbonInsertTab, has a group with a Hyperlink and a Bookmark button, whose drop-down menus insert, edit and delete hyperlinks and bookmarks. Connect the tab with the RibbonInsertTab property of the TextControl, as described in Connecting Ribbon Tabs in XAML. The hyperlink dialog box sets the text and either an address or a bookmark:

Insert Hyperlinks and Bookmarks in Code
private void InsertHyperlink_Click(object sender, RoutedEventArgs e)
{
HypertextLink link = new HypertextLink("Text Control website", "https://www.textcontrol.com/")
{
// The text screen readers use for the link
DescriptiveText = "Visit the Text Control website"
};
textControl1.HypertextLinks.Add(link);
}
private void InsertBookmark_Click(object sender, RoutedEventArgs e)
{
// A bookmark named "summary" at the input position
textControl1.DocumentTargets.Add(new DocumentTarget("summary"));
}
private void InsertLinkToBookmark_Click(object sender, RoutedEventArgs e)
{
DocumentTarget target = textControl1.DocumentTargets.GetItem("summary");
if (target != null)
{
textControl1.DocumentLinks.Add(new DocumentLink("See the summary", target));
}
}
- A HypertextLink has a visible text and a Target, for example a URL. DescriptiveText, available since version 34.0, describes the link for screen readers.
- A DocumentTarget is invisible and has a target name. DocumentTargetCollection.Add inserts it at the input position, and GetItem finds it by name or ID.
- A DocumentLink has a visible text and points to a
DocumentTarget. DocumentLinkCollection.Add inserts it.
Hypertext links aren't limited to HTML: they are saved and loaded with RTF, DOC and DOCX documents and with the TX Text Control format as well.
Saved as HTML with StreamType.HTMLFormat, a link opens its target in a browser:

Format a Link Like a Link
A link inserted in code gets no formatting of its own: it isn't blue and underlined, so users can't tell it from the text around it:

To show it as a link, format its text through a Selection object:
private void FormatHyperlink(HypertextLink link)
{
// TextField.Start is one-based, Selection.Start zero-based
Selection linkText = new Selection(link.Start - 1, link.Length)
{
ForeColor = System.Drawing.Color.Blue,
Underline = FontUnderlineStyle.Single
};
textControl1.Selection = linkText;
// Keep the input position inside the link, so that text typed
// behind it doesn't take the link formatting
textControl1.InputPosition = new InputPosition(linkText.Start + linkText.Length,
TextFieldPosition.InsideTextField);
}
Call it right after HypertextLinks.Add, or for each link of textControl1.HypertextLinks after loading a document.
- Start is the first character of a field, counted from 1, and Length its number of characters. A
Selectioncounts from 0, so the code subtracts 1. - ForeColor and Underline are applied when the object is assigned to Selection of the
TextControl.ForeColoris aSystem.Drawing.Color, not a WPF color. - The InputPosition constructor with a value of TextFieldPosition sets the input position at the end of the link.
InsideTextFieldputs it inside the field,OutsideTextFieldbehind it; the difference only exists for fields withDoubledInputPosition.
Edit an Existing Link
A dialog box of your own that inserts and edits links works with the link at the input position. HypertextLinkCollection.GetItem without a parameter returns it, or null when there is none:

private void EditHyperlink(string text, string target)
{
HypertextLink link = textControl1.HypertextLinks.GetItem();
if (link != null)
{
// The input position is in a link: change it
link.Text = text;
link.Target = target;
}
else
{
// Otherwise insert a new link at the input position
textControl1.HypertextLinks.Add(new HypertextLink(text, target) { DoubledInputPosition = true });
}
}
Before the dialog box opens, fill its text boxes from the link: link.Text and link.Target when GetItem returns one, and otherwise textControl1.Selection.Text, the text the user has selected, as the suggested link text. Text replaces the visible text and Target the address. HypertextLinkCollection.Remove deletes a link.
List, Show and Remove Bookmarks
Bookmarks have no visible text, so users need a list to find them again. A dialog box of your own inserts a bookmark with a name the user enters, and sets Deleteable to decide whether users can delete it while editing:

A second dialog box lists the bookmarks of the document and moves to or deletes the selected one:

The following handlers fill a WPF ListBox named bookmarkList with the bookmarks, and scroll to or remove the selected one:
private void FillBookmarkList()
{
bookmarkList.Items.Clear();
foreach (DocumentTarget target in textControl1.DocumentTargets)
{
bookmarkList.Items.Add(target.TargetName);
}
}
private void GoToBookmark_Click(object sender, RoutedEventArgs e)
{
if (bookmarkList.SelectedItem is string name)
{
textControl1.DocumentTargets.GetItem(name)?.ScrollTo();
}
}
private void RemoveBookmark_Click(object sender, RoutedEventArgs e)
{
if (bookmarkList.SelectedItem is string name)
{
DocumentTarget target = textControl1.DocumentTargets.GetItem(name);
if (target != null)
{
textControl1.DocumentTargets.Remove(target);
FillBookmarkList();
}
}
}
- TargetName is the name the bookmark was created with, and
GetItemfinds a bookmark by it. - ScrollTo moves the input position to the bookmark and scrolls it into view.
- DocumentTargetCollection.Remove removes the bookmark and returns
falsewhen it can't.
The same list fills the combo box of a dialog box that inserts links to bookmarks:

Bookmarks can be made visible with DocumentTargetMarkers. As a WPF property, it can be bound to a check box in XAML:
<CheckBox Content="Show bookmarks"
IsChecked="{Binding ElementName=textControl1, Path=DocumentTargetMarkers}" />
Handle Clicks on Links

Clicking a link doesn't open anything by itself, because applications differ in when a link is followed, for example only while the Ctrl key is held down. The TextControl raises HypertextLinkClicked or DocumentLinkClicked, and your handlers decide what to do. Connect them in XAML:
<WPF:TextControl x:Name="textControl1"
HypertextLinkClicked="textControl1_HypertextLinkClicked"
DocumentLinkClicked="textControl1_DocumentLinkClicked" />
private void textControl1_HypertextLinkClicked(object sender, HypertextLinkEventArgs e)
{
// Open the target of the link in the default browser
Process.Start(new ProcessStartInfo(e.HypertextLink.Target) { UseShellExecute = true });
}
private void textControl1_DocumentLinkClicked(object sender, DocumentLinkEventArgs e)
{
e.DocumentLink.DocumentTarget?.ScrollTo();
}
Process needs using System.Diagnostics;.
- HypertextLinkEventArgs contains the clicked link. When users open documents from other sources, check the target, for example for
https://, before you start it. - DocumentLinkEventArgs contains the document link. Its DocumentTarget is
nullwhen the bookmark has been deleted; otherwise ScrollTo moves the input position to it.
Tables of contents consist of document links too; see Tables of Contents. The TX Text Control Words sample Open Hyperlink, installed in %USERPROFILE%\Documents\TX Text Control 34.0.NET for WPF\CSharp\TX Words\Open Hyperlink, handles URLs, bookmarks and links to other documents.
Open Links to Local Files
The target of a hypertext link can also be the path of a file, absolute or relative, such as C:\Documents\contract.docx or appendix.docx. The following HypertextLinkClicked handler, based on the Open Hyperlink sample, opens a document that TX Text Control can load in a new instance of your own application, and passes every other file and every URL to Windows, which opens it with the associated application:
private static readonly Dictionary<string, StreamType> documentFormats =
new Dictionary<string, StreamType>(StringComparer.OrdinalIgnoreCase)
{
[] = StreamType.WordprocessingML,
[] = StreamType.MSWord,
[] = StreamType.RichTextFormat,
[] = StreamType.InternalUnicodeFormat
};
private void textControl1_HypertextLinkClicked(object sender, HypertextLinkEventArgs e)
{
string target = e.HypertextLink.Target;
if (string.IsNullOrEmpty(target))
{
return;
}
// An absolute URI, or else a path relative to the working folder
if (!(Uri.TryCreate(target, UriKind.Absolute, out Uri uri)
|| Uri.TryCreate(Path.GetFullPath(target), UriKind.Absolute, out uri)))
{
return;
}
if (!uri.IsFile)
{
// A URL: let Windows open it in the default browser
Process.Start(new ProcessStartInfo(uri.AbsoluteUri) { UseShellExecute = true });
return;
}
// Remove a jump mark such as #chapter2 from the path
string path = uri.LocalPath;
int hash = path.IndexOf('#');
if (hash >= 0)
{
path = path.Substring(0, hash);
}
if (!File.Exists(path))
{
MessageBox.Show(this, "The file " + path + " doesn't exist.");
return;
}
if (documentFormats.ContainsKey(Path.GetExtension(path)))
{
// A document: open it in a new instance of this application
Process.Start(new ProcessStartInfo(Environment.ProcessPath) { Arguments = "\"" + path + "\"" });
}
else
{
// Any other file: open it with its associated application
Process.Start(new ProcessStartInfo(path) { UseShellExecute = true });
}
}
The handler needs using System;, using System.Collections.Generic;, using System.Diagnostics; and using System.IO;, and it replaces the HypertextLinkClicked handler above.
Uri.IsFiletells a file from a URL. A relative path isn't an absolute URI, soPath.GetFullPathturns it into one first.Environment.ProcessPathis the path of the running application, and the quoted file path is its command-line argument. The new instance loads the file when its window opens, as Control Initialization and Document Access shows.UseShellExecute = truelets Windows choose the application for a URL or a file.
A document from an unknown source can link to any file or program. Decide which targets your application may open before it starts one; Handling Untrusted Documents shows a handler that opens only web addresses.
Next Steps
- Form Fields and Conditional Instructions: fields that users fill out.
- Subtext Parts: mark longer text ranges.
- Merge Fields: merge data into
MERGEFIELDfields. - Fields and Subtext Parts in Document Processing: fields without a user interface.