Selections and Caret Position
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
Selectionobject 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 action | Result |
|---|---|
| Click | Moves the caret to the point of the click, or selects an image or a text frame |
| Click and drag | Selects the text between the point where the button is pressed and the point where it is released |
| Shift+click | Extends the selection to the point of the click |
| Double-click | Selects the word |
| Double-click and drag | Extends the selection word by word |
| Triple-click | Selects the paragraph |
| Triple-click and drag | Extends the selection paragraph by paragraph |
| Click in the left or right margin | Selects 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, whichTextControlinherits from the Windows FormsControlclass, replaces the whole content with plain text.Select(18, 4)selects "1234", andSelection.Textreads 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 totextControl1.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
| Property | Contains |
|---|---|
| Start | The text position where the selection begins, starting with 0. Setting it keeps the current length. |
| Length | The 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. |
| Text | The 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:
| Property | Contains |
|---|---|
| Page | The page number, only when ViewMode is ViewMode.PageView |
| Line | The line on the page, starting with 1 |
| Column | The column in the line, starting with 0 in front of the first character |
| TextPosition | The text position, starting with 0 |
| Section | The 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:
MouseMoveis the event of the Windows FormsControlclass, ande.Locationthe position of the mouse pointer in pixels relative to the control. The code needsusing System.Drawing;forRectangle.TextChars.GetItem(e.Location, false)returns a TextChar only when the point is inside the rectangle of a character, andnullotherwise. Withtrueas the second parameter, it returns the nearest character.Lines.GetItem(e.Location)returns the line at the point, ornull.Lines.GetItemalso 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
| Property | Effect |
|---|---|
| HideSelection | Whether the selected text stays highlighted when the editor loses the focus, for example to a dialog box of your application |
| SelectionViewMode | TransparentBitmap shows the selection with a transparent highlight, Classic inverts the selected text |
Next Steps
- Clipboard Operations: copy, cut and paste the selection.
- Search and Replace: find text and select every match.
- Text and Paragraph Formatting: format the selection.
- Events: the other events of the editor.