Selections and Caret Position
Where is the user typing, and which text will the next command change? In a WPF TextControl, the answer is always the input position or the selection. Your code reads them to react to the user and sets them to insert, replace or format text at a known place in the document.
The examples use a window with a TextControl named textControl1, as in Create Your First WPF Application. Formatting the selected text is covered on Text and Paragraph Formatting.
Text Positions and the Input Position
Characters are counted from 0: text position 0 lies in front of the first character, and a selection with the start 18 and the length 4 covers the 19th to the 22nd character. Select, the Selection class and Find all use these numbers.
The input position is where typed text appears, marked by the caret. Without selected text, the selection has a length of 0, and its start is the input position. With selected text, typing replaces it.
The TextControl of the TXTextControl.WPF namespace has two properties for this:
- 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 it.
Selection and InputPosition are classes of the TXTextControl namespace, the same as in the Windows Forms product. Both refer to the text part with the input focus, which is the main text, a text frame, or a header or footer.
Select Text With the Mouse and Keyboard
Users select text without any code in your application:
| 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 points where the button is pressed and 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 tables, 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 a cell. Every key that moves the caret extends the selection while Shift is held down. All keys are listed on Keyboard Shortcuts.
Select and Replace Text in Code
Select selects a number of characters from a start position, SelectAll the whole text and SelectWord the word at the input position. Selection.Text then returns the selected text, and setting it replaces the selection.
The control can be used once its Loaded event has occurred. In MainWindow.xaml, connect the event handlers of this page to the control:
<Window x:Class="SampleApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:WPF="clr-namespace:TXTextControl.WPF;assembly=TXTextControl.WPF"
Title="MainWindow" Height="450" Width="800">
<Grid>
<WPF:TextControl x:Name="textControl1"
Loaded="TextControl1_Loaded"
InputPositionChanged="TextControl1_InputPositionChanged" />
</Grid>
</Window>
Replace SampleApp with the namespace of your project. The Loaded handler in MainWindow.xaml.cs writes a sentence into the editor, reads and replaces the order number and formats the first word:
using System;
using System.Windows;
using TXTextControl;
namespace SampleApp;
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void TextControl1_Loaded(object sender, RoutedEventArgs 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();
}
private void TextControl1_InputPositionChanged(object sender, EventArgs e)
{
InputPosition position = textControl1.InputPosition;
Title = $"Page {position.Page}, line {position.Line}, column {position.Column}, " +
$"text position {position.TextPosition}, selected: {textControl1.Selection.Length}";
}
}
Run the application: the editor shows "Invoice for order 5678. Thank you for your order.", and the title bar of the window follows the caret as you click and type.
The code works as follows:
- Text replaces the whole content with plain text.
Select(18, 4)selects "1234".Selection.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) puts the caret in front of the first character.
- InputPositionChanged occurs whenever the caret moves, whether by typing, a click or code. The handler writes the position into the
Titleof the window.
Prepare a Selection Object or Change the Current Selection
Text is changed either by selecting it with Select and setting the properties of textControl1.Selection one by one, or by preparing a Selection object and assigning it once, as the code does for "Invoice".
The prepared object doesn't show a visible selection while it is applied, and it counts as a single step for Undo and Redo. Each property set on the current selection is a step of its own. The Selection constructor without parameters creates an object whose Text and formatting are inserted at the input position when you assign it.
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. |
When the selected text has different values for an attribute, for example partly bold text, the property returns its default value, and IsCommonValueSelected returns false for that attribute.
To exchange the selection as formatted text, Selection.Save writes it to a file, a byte array or a string in a format such as HTML, RTF or DOCX, and Selection.Load replaces it 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);
Move the Input Position
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 |
| PageInSection | The page number within the section, only in ViewMode.PageView |
| Location | The position of the caret in twips, from the top-left corner of the document |
| CaretSize | The size of the caret at the input position, in pixels |
Except for InactiveMarker, the properties are read-only; to move the caret, assign a new object. The InputPosition constructor takes a text position, or a page, a line and a column. For a text position at the start or end of a field, a value of TextFieldPosition decides whether the caret lies inside or outside the field.
// 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 caret into view. Without a parameter, it chooses the position itself.
InactiveMarker is the one property that can be set: with true, a marker shows the input position while the editor is inactive and the blinking caret isn't visible.
Read Text Without Selecting It
To read text without moving the caret or the visible selection, 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.
Find Lines and Characters on the Screen
Each formatted line and each character also has a position on the page. The Line and TextChar objects of the Lines and TextChars collections report it, for example to place a WPF element next to a line or to show a tooltip for the word under the mouse pointer. Their measures are in twips, 1,440 to the inch, from the top-left corner of the whole document.
The following handler of a button with Click="ShowLine_Click" describes the line with the caret in the title bar and gets the line as HTML:
using System.Windows;
using TXTextControl;
private void ShowLine_Click(object sender, RoutedEventArgs e)
{
// GetItem takes a zero-based text position
TXTextControl.Line line = textControl1.Lines.GetItem(textControl1.InputPosition.TextPosition);
// Bounds and baseline in twips, relative to the document
System.Drawing.Rectangle bounds = line.TextBounds;
// The same point in the coordinates of the control
Point topLeft = textControl1.PointFromDocument(new Point(bounds.Left, bounds.Top));
// The text of the line as HTML
string html;
line.Save(out html, StringStreamType.HTMLFormat);
Title = $"Line {line.Number} on page {line.Page}, {bounds.Width} twips wide, " +
$"baseline at {line.Baseline} twips, shown at {topLeft.X:0}, {topLeft.Y:0}";
}
The code works as follows:
- LineCollection.GetItem returns the line at a zero-based text position, or at a point of the control. It returns
nullif there is no line there. - TextBounds is the rectangle around the text of the line and Baseline the vertical position of its baseline. Number, Page, Start and Length give the place of the line in the text;
Startcounts from 1. - PointFromDocument converts a point in document coordinates into the coordinates of the
TextControl. PointToDocument converts the other way, and ScrollLocation is the top-left corner of the visible part of the document. - Line.Save saves the text of the line to a string or a byte array in a format such as HTML or RTF.
TXTextControl.Line is written with its namespace because System.Windows.Shapes, which the WPF project template imports, has a Line class too.
Find the Character Under the Mouse Pointer
TextCharCollection.GetItem returns the character at a point of the control. Connect PreviewMouseMove="TextControl1_PreviewMouseMove" to the TextControl in MainWindow.xaml and add this handler:
using System.Windows;
using System.Windows.Input;
using TXTextControl;
private void TextControl1_PreviewMouseMove(object sender, MouseEventArgs e)
{
Point mouse = e.GetPosition(textControl1);
// false: only a character whose bounds contain the point
TextChar character = textControl1.TextChars.GetItem(
new System.Drawing.Point((int)mouse.X, (int)mouse.Y), false);
if (character != null)
{
Title = $"Character {character.Number}: '{character.Char}', bounds {character.Bounds}";
}
}
The point is given in pixels from the top-left corner of the control. GetPosition returns WPF's device-independent units, which match pixels at a display scaling of 100 percent; DPI and Display Scaling covers other settings. With true as the second parameter, GetItem returns the nearest character instead of null. Bounds is the rectangle of the character in twips, relative to the document, and Number its position, starting with 1.
TextCharCollection.Add inserts a single character or a control character at the input position or at a text position, and TextCharCollection.Remove removes one; control characters that other formatting depends on, such as the end of a table cell, can't be removed. The LineCollection and TextCharCollection of headers, footers and text frames work the same way.
Select Tables and Objects
Tables are selected by their parts: Table.Select, TableRow.Select, TableColumn.Select and TableCell.Select. See Tables and Table Formulas.
For objects placed behind the text, SelectObjects decides what a click selects: with true, the object; with false, the text in front of it.
Change How the Selection and the Caret Look
| Property | Effect |
|---|---|
| HideSelection | Whether the selected text stays highlighted when the editor loses the keyboard focus |
| SelectionViewMode | TransparentBitmap shows the selection with a transparent highlight, Classic inverts the selected text |
| CaretWidth | The width of the caret in pixels, up to 255; 0 uses the system default |
All three can be set as attributes in XAML, for example HideSelection="False".
Next Steps
- Clipboard Operations: copy, cut and paste the selection.
- Search and Replace: find text and select each match.
- Text and Paragraph Formatting: format the selection.
- Events: the other events of the editor.