TX Text Control .NET for Windows Forms

Selections and Caret Position

Topic

Every change a user or your code makes in a TextControl happens at the input position or in the selection. The caret marks the input position, and a selection is the highlighted range of text that the next command acts on. Your code reads both to find out where the user is working, and sets both to insert, replace or format text at a known place.

The examples extend the form of Create Your First Windows Forms Application, whose editor is named textControl1. Formatting the selected text is covered on Text and Paragraph Formatting.

Text Positions and the Input Position

A text position is the index of a character in the text, counted from 0. Position 0 lies in front of the first character, so a selection that starts at 18 and has a length of 4 covers the 19th to the 22nd character. The same numbers are used by Select, by the Selection class and by Find.

The input position is where typed text appears. When nothing is selected, the selection has a length of 0 and its start is the input position. As soon as the user selects text, the selection has a start and a length, and typing replaces the selected text.

Two properties of the TextControl give access to both:

  • Selection returns a Selection object for the current selection, and accepts a new one.
  • InputPosition returns an InputPosition object with the page, line and column of the caret, and accepts a new one to move the caret.

Both work in the text part that has the input focus: the main text, a text frame, or a header or footer. When the user edits a header, Selection refers to the header text.

Select Text With the Mouse and Keyboard

The editor selects text without any code. The following mouse actions are built in:

Mouse actionResult
ClickMoves the caret to the point of the click, or selects an image or a text frame
Click and dragSelects the text between the point where the button is pressed and the point where it is released
Shift+clickExtends the selection to the point of the click
Double-clickSelects the word
Double-click and dragExtends the selection word by word
Triple-clickSelects the paragraph
Triple-click and dragExtends the selection paragraph by paragraph
Click in the left or right marginSelects the line

In a table, a click on the top of a column selects the column, a click in the margin next to a row selects the row, and a double-click selects the cell. With the keyboard, any key that moves the caret extends the selection when Shift is held down, for example Shift+Right Arrow or Ctrl+Shift+End. Keyboard Shortcuts lists all keys.

Select and Replace Text in Code

Select takes a start position and a number of characters. SelectAll selects the whole text, and SelectWord the word at the input position. Afterward, Selection.Text returns the selected text, and setting it replaces the selection.

The following code in Form1.cs writes a sentence into the editor when the form opens, reads and replaces the order number and formats the first word:

using System;
using System.Windows.Forms;
using TXTextControl;

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
        Load += Form1_Load;
    }

    private void Form1_Load(object sender, EventArgs e)
    {
        textControl1.Text = "Invoice for order 1234. Thank you for your order.";

        // Select the four characters "1234", which start at text position 18
        textControl1.Select(18, 4);
        string orderNumber = textControl1.Selection.Text;

        // Replace the selected text
        textControl1.Selection.Text = "5678";

        // Format "Invoice" with a prepared Selection object
        Selection title = new Selection(0, 7);
        title.Bold = true;
        title.Italic = true;
        textControl1.Selection = title;

        // Move the input position to the start of the text
        textControl1.InputPosition = new InputPosition(0);
        textControl1.Focus();
    }
}

Keep the namespace that Visual Studio created around the class. When the form opens, it shows "Invoice for order 5678. Thank you for your order." with "Invoice" in bold italics and the caret in front of it.

The code works as follows:

  • Text, which TextControl inherits from the Windows Forms Control class, replaces the whole content with plain text.
  • Select(18, 4) selects "1234", and Selection.Text reads it and then replaces it with "5678".
  • new Selection(0, 7) creates a Selection object for the first seven characters. Its properties, such as Bold, are applied to that range when the object is assigned to textControl1.Selection.
  • new InputPosition(0) places the caret in front of the first character without selecting text.

Prepare a Selection Object or Change the Current Selection

There are two ways to change a range of text. You can select it with Select and set the properties of textControl1.Selection one by one, or you can prepare a new Selection object and assign it once, as the code above does for "Invoice".

The prepared object has two advantages: the user doesn't see the text being selected, and the change counts as one step for Undo and Redo, where every single property set on the current selection is a step of its own. Selection has a constructor without parameters, too. Set its Text and formatting, and the assignment inserts the formatted text at the input position.

Selection Properties

PropertyContains
StartThe text position where the selection begins, starting with 0. Setting it keeps the current length.
LengthThe number of selected characters. With 0, Start is the input position. A start of 0 and a length of int.MaxValue select the whole text.
TextThe selected text as a string. Setting it replaces the selection.

A selection that spans text with different formatting has no common value for an attribute such as bold. In that case, the property returns its default value, and IsCommonValueSelected returns false for that attribute.

The selected text can also be exchanged as formatted text. Selection.Save writes the selection to a file, a byte array or a string in a format such as HTML, RTF or DOCX, and Selection.Load replaces the selection with formatted content:

// Get the selected text as HTML
string html;
textControl1.Selection.Save(out html, StringStreamType.HTMLFormat);

// Replace the selection with formatted text
textControl1.Selection.Load("<p>Paid in <strong>full</strong></p>", StringStreamType.HTMLFormat);

Read the Input Position

The InputPositionChanged event occurs whenever the caret moves, by typing, clicking or code. The following handler shows the position in the title bar of the form. Connect it in the constructor with textControl1.InputPositionChanged += TextControl1_InputPositionChanged;:

private void TextControl1_InputPositionChanged(object sender, EventArgs e)
{
    InputPosition position = textControl1.InputPosition;

    Text = $"Page {position.Page}, line {position.Line}, column {position.Column}, " +
        $"text position {position.TextPosition}, selected: {textControl1.Selection.Length}";
}

Here, Text is the caption of the form. The InputPosition object describes the caret:

PropertyContains
PageThe page number, only when ViewMode is ViewMode.PageView
LineThe line on the page, starting with 1
ColumnThe column in the line, starting with 0 in front of the first character
TextPositionThe text position, starting with 0
SectionThe section, starting with 1

These properties are read-only. To move the caret, assign a new object. The InputPosition constructor takes a text position, a page, a line and a column, or a point in pixels relative to the upper left corner of the control. A text position at the start or end of a field can lie inside or outside the field, and a value of TextFieldPosition decides which:

// Move the caret to page 1, line 1, column 0
textControl1.InputPosition = new InputPosition(1, 1, 0);

// Scroll the caret to the top of the visible area
textControl1.InputPosition.ScrollTo(InputPosition.ScrollPosition.Top);

ScrollTo scrolls the document so that the caret is visible. Without a parameter, it chooses the position itself.

Read Text Without Selecting It

Selecting text moves the caret and the visible selection. To read text without changing either, use the collections of the TextControl:

// The paragraph at the input position
string paragraphText = textControl1.Paragraphs.GetItem(textControl1.InputPosition.TextPosition).Text;

// The first line of the document
string firstLine = textControl1.Lines[1].Text;

// The fifth character
char fifthCharacter = textControl1.TextChars[5].Char;
  • Paragraphs contains the paragraphs. GetItem returns the paragraph at a text position, and Text its text.
  • Lines contains the lines, numbered from 1.
  • TextChars contains the characters, numbered from 1.

Like Selection, both collections belong to the text part with the input focus. A Line also knows its Number, its first character in Start (counted from 1), its Length including the break character and the Page it is on. Line.Save writes the text of one line to a string or a byte array in a format such as HTML or RTF, with the same StringStreamType and BinaryStreamType values as Selection.Save.

Find the Line and Character at a Point

Every line and every character has a position on the page. Line.TextBounds is the rectangle around the text of a line, Line.Baseline the position of its baseline, and TextChar.Bounds the rectangle around a single character. All three are given in twips, with the origin at the upper left corner of the whole document, not of the visible part.

The other way round, TextCharCollection.GetItem and LineCollection.GetItem take a point in pixels relative to the upper left corner of the control and return the character or the line there. That is what you need to react to the character under the mouse pointer, for example to show a tooltip. The following handler shows the character and its line in the caption of the form. Connect it in the constructor with textControl1.MouseMove += TextControl1_MouseMove;:

private void TextControl1_MouseMove(object sender, MouseEventArgs e)
{
    // true returns the nearest character, false only a character under the point
    TextChar textChar = textControl1.TextChars.GetItem(e.Location, false);
    Line line = textControl1.Lines.GetItem(e.Location);

    if (textChar == null || line == null)
    {
        Text = "No text under the mouse pointer";
        return;
    }

    // Convert the document coordinates of the character (twips) into client coordinates
    Rectangle charBounds = textControl1.DocumentToClient(textChar.Bounds);

    Text = $"Character '{textChar.Char}' (number {textChar.Number}) in line {line.Number} " +
        $"on page {line.Page}, at {charBounds.Location} in the control";
}

The code works as follows:

  • MouseMove is the event of the Windows Forms Control class, and e.Location the position of the mouse pointer in pixels relative to the control. The code needs using System.Drawing; for Rectangle.
  • TextChars.GetItem(e.Location, false) returns a TextChar only when the point is inside the rectangle of a character, and null otherwise. With true as the second parameter, it returns the nearest character.
  • Lines.GetItem(e.Location) returns the line at the point, or null. Lines.GetItem also accepts a text position.
  • DocumentToClient converts a point or a rectangle in document twips into client coordinates of the control, and ClientToDocument converts client coordinates into document twips. ScrollLocation gives the upper left corner of the visible part in document twips, the link between the two coordinate systems.

The InputPosition constructor that takes a point in pixels moves the caret to the character found there.

Insert and Remove Single Characters

TextCharCollection.Add inserts one character at the input position, or at a text position you pass, optionally in a given font. Another overload inserts a control character, one of the ControlChars values such as LineBreak, PageBreak or NonBreakingSpace. TextCharCollection.Remove deletes a character and returns false if it can't, for example for a table cell end character that other formatting depends on:

// Insert a copyright sign in the font Arial at text position 0
textControl1.TextChars.Add('©', "Arial", 0);

// Insert a line break at the input position
textControl1.TextChars.Add(ControlChars.LineBreak);

// Remove the first character of the text
bool removed = textControl1.TextChars.Remove(textControl1.TextChars[1]);

Both methods were introduced in version 23.0. To let the user pick a symbol instead, open the built-in dialog box with AddSymbolDialog; Dialog Boxes lists the other built-in dialog boxes.

Select Tables and Objects

Table parts are not selected with character positions. Table.Select, TableRow.Select, TableColumn.Select and TableCell.Select select a whole table, a row, a column or a cell; see Tables and Table Formulas.

Images, text frames and other objects can be placed behind the text. SelectObjects decides what a click selects there: with true, the object behind the text; with false, the text in front of it.

Change How the Selection Looks

PropertyEffect
HideSelectionWhether the selected text stays highlighted when the editor loses the focus, for example to a dialog box of your application
SelectionViewModeTransparentBitmap shows the selection with a transparent highlight, Classic inverts the selected text

Next Steps