Skip to content

RichEditBox

Header: winui/ui/text/RichEditBox.hpp. Spec: winui/ui/text/RichEditBox.hpp.

An editable, formatted document that looks like a TextBox.

The document is plain text (\n between paragraphs) with a character format per character. The user edits it as in a multi-line TextBox, formats with Ctrl+B/I/U or the context menu, and undoes with Ctrl+Z/Ctrl+Y. The app reads and writes it as text(), spans() or RTF, and formats the selection with set_selection_format(). Only the user's changes raise on_text_changed(), never changes made from code. In a spec tree it is built by ui::RichEditBox.

Example:

std::string draft; // the saved document, as RTF
auto  panel  = std::make_unique<ui::StackControl>(ui::Orientation::Vertical);
auto* bold   = panel->add(std::make_unique<ui::ButtonControl>("Bold"));
auto* editor = panel->add(std::make_unique<ui::RichEditBoxControl>());
editor->set_header("Notes");
editor->set_placeholder_text("Type here");
editor->set_height(240.0f);
editor->load_rtf(R"({\rtf1\ansi Hello, {\b world}!\par})");
bold->on_click([editor] { editor->toggle_bold(); });
editor->on_text_changed([editor, &draft] { draft = editor->save_rtf(); });
window.set_content(std::move(panel));

ui::RichEditBox

Editable rich text: character formatting, undo, RTF in and out. Builds a ui::RichEditBoxControl.

Formatting is applied through the built control (via .control); the spec only sets up the document and its editing rules.

Example:

ui::Control<ui::RichEditBox> editor;
bool                dirty  = false;

window.set_content(ui::RichEditBox{
    .header           = "Notes",
    .placeholder_text = "Write here",
    .height           = 200,
    .on_text_changed  = [&] { dirty = true; },
    .control          = editor,
});

Every field, in the order a designated initializer names them:

ui::RichEditBox{
    .header, .description, .error, .placeholder_text, .text, .max_length, .spell_check,
    .read_only, .accepts_return, .character_casing, .disabled_formatting_shortcuts, .wrapping,
    .text_alignment, .font_family, .font_size, .foreground, .selection_highlight_color, .style,
    .margin, .width, .height, .min_width, .min_height, .max_width, .max_height,
    .horizontal_alignment, .vertical_alignment, .visible, .tool_tip, .enabled, .accessibility,
    .on_text_changed, .on_selection_changed, .on_pointer_entered, .on_pointer_exited,
    .on_got_focus, .on_lost_focus, .on_key_down, .on_wheel, .control
}

Fields, in declaration order (designators must follow it):

Field Type Default
header ui::Binding<std::string> The label above the control. Empty draws none.
description ui::Binding<std::string> Secondary text under the box, such as what to enter or why; empty draws none. Screen readers offer it as help text.
error ui::Binding<std::string> What is wrong with the value: non-empty shows it in the description's place in the critical color and draws the border in that color, and screen readers report the field invalid. The field shows exactly what it is given, so bind it to a check of the value and decide there when it shows (after the first submit, say).
placeholder_text ui::Binding<std::string> The hint shown while the document is empty.
text ui::TwoWayField<std::string> The document as plain text; a new value replaces it (and its formatting), one equal to what the box holds changes nothing. ui::bind(prop) is two-way: what the user types is written back as plain text, before .on_text_changed runs.
max_length ui::Binding<std::size_t> {0} The most characters the user can type or paste; 0 is no limit.
spell_check ui::Binding<bool> {true} Whether misspelled words are underlined, with suggestions in the context menu.
read_only ui::Binding<bool> {false} Selectable and copyable, never edited.
accepts_return ui::Binding<bool> {true} Enter starts a new paragraph. On by default.
character_casing ui::Binding<ui::CharacterCasing> {ui::CharacterCasing::Normal} Sets the casing of what the user types or pastes.
disabled_formatting_shortcuts ui::Binding<ui::DisabledFormattingShortcuts> {ui::DisabledFormattingShortcuts::None} The Ctrl+B/I/U shortcuts to leave out.
wrapping ui::Binding<ui::TextWrapping> {ui::TextWrapping::Wrap} Whether lines break at the box's width; Wrap by default.
text_alignment ui::Binding<ui::TextAlignment> {ui::TextAlignment::Left} The alignment of every paragraph.
font_family ui::Binding<std::optional<std::string>> The default font family; unset follows the theme.
font_size ui::Binding<std::optional<float>> The default font size, DIPs; unset follows the theme.
foreground ui::Binding<ui::Paint> The color of text without its own: a fixed color or a theme resource; unset follows the theme.
selection_highlight_color ui::Binding<ui::Paint> The color behind selected text: a fixed color or a theme resource, which follows theme switches. Unset is the accent.
on_text_changed ui::Callback<void(const std::string&)> Fired after every change the user makes to the text or its formatting. Handlers in a spec returned from a function must capture by value or capture state that outlives the window (see spec/Spec.hpp).
on_selection_changed ui::Action The caret moved or the selection grew or shrank.
on_pointer_entered ui::Action The pointer moved onto the control.
on_pointer_exited ui::Action The pointer left the control.
on_got_focus ui::Action Keyboard focus arrived on the control, or on anything inside it, from outside it. A control with nothing focusable in it never gets focus, and never raises this.
on_lost_focus ui::Action Keyboard focus left the control and everything inside it.
on_key_down ui::Callback<ui::Handling(const ui::KeyEvent&)> A key pressed while focus is on the control or inside it, before the control's own handling; ui::Handled stops it there (Element::on_key_down()).
on_wheel ui::Callback<ui::Handling(const ui::PointerEvent&)> A wheel turn over the control, before the control scrolls; ui::Handled stops it there (Element::on_wheel()).
control ui::ControlOut<ui::RichEditBox> Out: a non-owning observer of the built control, used to format text or read RTF.

Plus the common fields style, margin, width, height, min_width, min_height, max_width, max_height, horizontal_alignment, vertical_alignment, visible, tool_tip, enabled, accessibility every spec carries (conventions).

build

std::unique_ptr<Element> build()&&

Consumes the spec and returns the built ui::RichEditBoxControl; the caller owns it.

ui::RichEditBoxControl

Derives from ui::Element, ui::ValueProvider.

An editable, formatted document that looks like a TextBox.

The document is plain text (\n between paragraphs) with a character format per character. The user edits it as in a multi-line TextBox, formats with Ctrl+B/I/U or the context menu, and undoes with Ctrl+Z/Ctrl+Y. The app reads and writes it as text(), spans() or RTF, and formats the selection with set_selection_format(). Only the user's changes raise on_text_changed(), never changes made from code. In a spec tree it is built by ui::RichEditBox.

Example:

std::string draft; // the saved document, as RTF
auto  panel  = std::make_unique<ui::StackControl>(ui::Orientation::Vertical);
auto* bold   = panel->add(std::make_unique<ui::ButtonControl>("Bold"));
auto* editor = panel->add(std::make_unique<ui::RichEditBoxControl>());
editor->set_header("Notes");
editor->set_placeholder_text("Type here");
editor->set_height(240.0f);
editor->load_rtf(R"({\rtf1\ansi Hello, {\b world}!\par})");
bold->on_click([editor] { editor->toggle_bold(); });
editor->on_text_changed([editor, &draft] { draft = editor->save_rtf(); });
window.set_content(std::move(panel));

UndoLimit

static std::size_t UndoLimit = 100

Steps kept before the oldest is dropped.

SpellCheckDelayMs

static double SpellCheckDelayMs = 250.0

How long after the last edit the document is checked again, in milliseconds.

MaxSpellingSuggestions

static std::size_t MaxSpellingSuggestions = 5

The most suggestions the context menu offers for one misspelling.

CaretBlinkIntervalMs

static double CaretBlinkIntervalMs = 530.0

How long the caret stays shown, and then hidden, in milliseconds.

RichEditBoxControl

RichEditBoxControl()

An empty document.

RichEditBoxControl

RichEditBoxControl(std::string text)

A document of unformatted text, the caret at its end.

text

const std::string& text() const

The plain text, a \n for every paragraph mark.

set_text

void set_text(std::string text)

Replaces the document with unformatted text and puts the caret at its end. Like every change made from code, it raises no on_text_changed(). It clears the undo history.

spans

std::vector<RichTextSpan> spans() const

The document as runs of one format each, in order.

set_spans

void set_spans(const std::vector<RichTextSpan>& spans)

Replaces the document with formatted text; as set_text().

load_rtf

void load_rtf(std::string_view rtf)

Replaces the document with RTF. Text that is not RTF at all comes in as plain text.

save_rtf

std::string save_rtf() const

The document as RTF, with font and color tables for the formats it uses. load_rtf() reads it back.

caret

std::size_t caret() const

The caret's offset into text(): the moving end of the selection.

selection

std::pair<std::size_t, std::size_t> selection() const

The selection as [start, end) (the caret when they are equal).

has_selection

bool has_selection() const

Whether any text is selected (more than a caret).

select

void select(std::size_t start, std::size_t end)

Selects [start, end), the caret at end; both are clamped to the text.

select_all

void select_all()

Selects the whole document (Ctrl+A).

selected_text

std::string selected_text() const

The selected text, plain; empty at a bare caret.

selection_format

CharacterFormat selection_format() const

What the whole selection shares, or at a bare caret what will be typed next.

set_selection_format

void set_selection_format(const CharacterFormat& format)

Applies format to the selection as one undo step, or at a bare caret to what is typed next.

range_format

CharacterFormat range_format(std::size_t start, std::size_t end) const

What [start, end) shares.

set_range_format

void set_range_format(std::size_t start, std::size_t end, const CharacterFormat& format)

Applies format to [start, end), leaving the selection where it is. Not an undo step: it is for markup the app keeps over the document, such as highlighting search hits.

toggle_bold

void toggle_bold()

Toggles bold: off when the whole selection has it, on otherwise. Ctrl+B.

toggle_italic

void toggle_italic()

As toggle_bold(), for italic. Ctrl+I.

toggle_underline

void toggle_underline()

As toggle_bold(), for underline. Ctrl+U.

find_all

std::vector<std::pair<std::size_t, std::size_t>> find_all(std::string_view text) const

Searches the whole text, case-insensitive: every match as [start, end).

type_text

void type_text(std::string_view text)

Replaces the selection as if typed. The maximum length and character casing apply, it is an undo step and it fires on_text_changed(). Line breaks are kept only while accepts_return() is on.

can_undo

bool can_undo() const

Whether there is a step to undo.

can_redo

bool can_redo() const

Whether there is an undone step to redo.

undo

void undo()

Restores (Ctrl+Z) the text, formats and selection before the last step.

redo

void redo()

Reapplies (Ctrl+Y, Ctrl+Shift+Z) the last undone step.

const std::string& header() const

The label above the box; empty when none is shown.

set_header

void set_header(std::string header)

The label above the box. Empty draws none.

description

const std::string& description() const

The secondary text under the box; empty (the default) draws none.

set_description

void set_description(std::string description)

Sets the description. Empty draws none and takes no room.

error

const std::string& error() const

What is wrong with the value, shown in the description's place in the critical color, with the border in the same color; empty (the default) shows the description.

set_error

void set_error(std::string error)

Sets the error. Empty shows the description again.

automation_help_text_fallback

std::string automation_help_text_fallback() const override

The description, or the error while one is set, is the help text a screen reader offers when accessibility().description is empty.

automation_invalid

bool automation_invalid() const override

Invalid while an error is set.

automation_name_fallback

std::string automation_name_fallback() const override

The header names the control when accessibility().name does not.

placeholder_text

const std::string& placeholder_text() const

The hint shown while the document is empty.

set_placeholder_text

void set_placeholder_text(std::string text)

Drawn in a secondary text color while the document is empty.

font_family

const std::optional<std::string>& font_family() const

The font family of text without one of its own; unset follows the theme.

set_font_family

void set_font_family(std::optional<std::string> family)

Sets or clears the default font family; unset follows the theme. Text without a family of its own is drawn in it.

font_size

std::optional<float> font_size() const

The font size in DIPs of text without one of its own; unset follows the theme.

set_font_size

void set_font_size(OptionalFloat size)

Sets or clears the default font size; unset follows the theme.

foreground

const Paint& foreground() const

The color of text without a color of its own; unset follows the theme.

set_foreground

void set_foreground(Paint color)

Sets the text color; unset follows the theme. Text without a color of its own is drawn in it.

selection_highlight_color

std::optional<Color> selection_highlight_color() const

The fixed color behind selected text; nullopt (the default) when unset or a theme resource is set. Unset is the accent.

selection_highlight_resource

ThemeColor selection_highlight_resource() const

The color behind selected text as a theme resource, or nullptr.

set_selection_highlight_color

void set_selection_highlight_color(std::optional<Color> color)

Sets the fixed color behind selected text and clears a theme resource; nullopt goes back to the accent.

set_selection_highlight_color

void set_selection_highlight_color(ThemeColor resource)

Sets the color behind selected text to a theme resource, re-read under every theme, and clears a fixed one.

text_alignment

TextAlignment text_alignment() const

How each line sits between the edges; Left by default.

set_text_alignment

void set_text_alignment(TextAlignment alignment)

Sets the alignment of every paragraph.

base_style

TextStyle base_style(const Theme& theme) const

The style text without a format of its own is measured and drawn in.

read_only

bool read_only() const

Whether the text is read-only; false by default.

set_read_only

void set_read_only(bool read_only)

Read-only text can be selected and copied but not edited, and shows no caret. A read-only box drops its squiggles. Making it editable again checks the spelling again.

accepts_return

bool accepts_return() const

Whether Enter starts a new paragraph; true by default.

set_accepts_return

void set_accepts_return(bool accepts)

Enter starts a new paragraph and pasted line breaks are kept. On by default.

wrapping

TextWrapping wrapping() const

Whether lines break at the box's width; Wrap by default.

set_wrapping

void set_wrapping(TextWrapping wrapping)

Sets the wrapping mode.

max_length

std::size_t max_length() const

The most characters the user can type or paste; 0 is no limit.

set_max_length

void set_max_length(std::size_t length)

The most characters the user can type or paste; 0 (the default) is no limit. Text set from code is not cut.

character_casing

CharacterCasing character_casing() const

How typed text is cased; Normal by default.

set_character_casing

void set_character_casing(CharacterCasing casing)

Sets the casing of what the user types or pastes from now on.

disabled_formatting_shortcuts

DisabledFormattingShortcuts disabled_formatting_shortcuts() const

Which of Ctrl+B/I/U and the matching context menu commands are off; None by default.

set_disabled_formatting_shortcuts

void set_disabled_formatting_shortcuts(DisabledFormattingShortcuts disabled)

Turns off formatting shortcuts and their menu commands. Combine flags with |, such as Bold | Italic. Takes effect on the next key press or context menu.

on_text_changed

void on_text_changed(Callback<void(const std::string&)> handler)

Fired after every change the user makes to the text or its formatting, with the text.

on_selection_changed

void on_selection_changed(Callback<void()> handler)

Fired after the user moves the caret or the selection.

add_context_menu_item

void add_context_menu_item(std::string text, Action on_click)

Adds an item of the app's own to the end of the context menu ("Share", say).

focused

bool focused() const

Whether the box holds keyboard focus (the caret blinks).

focusable

bool focusable() const override

A tab stop while enabled, read-only or not.

spell_check

bool spell_check() const

Whether spell checking is on; true by default. Squiggles and the context menu's suggestions work as in TextBox. There is no autocorrection.

set_spell_check

void set_spell_check(bool enabled)

Turns spell checking on or off. Turning it off clears the squiggles, and turning it on checks the document again.

spelling_errors

const std::vector<SpellingError>& spelling_errors() const

The misspellings found in the last check, as offsets into text().

sync_spell_checker

void sync_spell_checker(SpellChecker& checker) override

Remembers the host's spell checker and schedules a check.

handle_context_menu

bool handle_context_menu(std::optional<Point> position, ContextMenuHost& host) override

Spelling suggestions for a misspelled word under position, then Cut, Copy, Paste, Bold, Italic, Underline, Undo, Redo and Select All where they apply, then the items from add_context_menu_item().

caret_visible

bool caret_visible() const

Whether a focused box draws its caret right now, in its blink cycle.

automation_role

AutomationRole automation_role() const override

AutomationRole::Edit.

caret_bounds

std::optional<Rect> caret_bounds() const override

The caret, one DIP wide and a line tall, while focused and editable.

measure_override

Size measure_override(Size available, const MeasureContext& context) const override

As TextBox: as wide as the text or the placeholder and as tall as its lines, within what is offered, plus the header above.

arrange_override

void arrange_override(const Rect& final_rect, const ArrangeContext& context) override

Places the header and the text area.

pointer_override

bool pointer_override(const PointerEvent& e) override

Click places the caret (Shift+click extends), drag selects, double-click selects a word, triple-click a paragraph.

wheel_override

bool wheel_override(const PointerEvent& e) override

Scrolls the text vertically; false when there is nothing to scroll, so an enclosing scroller gets the turn.

tick_override

void tick_override(Duration delta) override

Blinks the caret and runs a pending spell check once its delay has passed.

render_override

void render_override(Renderer& renderer, const RenderContext& context) const override

The header, the field chrome, the placeholder or the formatted text (scrolled), the selection, the squiggles and the caret.

enabled_changed

void enabled_changed() override

is_enabled() flipped, through set_enabled() here or on an ancestor, or a move to another parent. Drop any press or hover state. The element is redrawn either way. No-op by default.

focus_changed

void focus_changed(bool focused) override

Called by Window as focus moves: restarts the caret blink.

key_override

bool key_override(const KeyEvent& e) override

Caret movement, deletion, clipboard, undo/redo and the formatting shortcuts.

text_override

bool text_override(const TextInputEvent& text_event) override

Typed text replaces the selection in the typing format, with the max length and character casing applied. Consecutive typing is one undo step.

draws_focus_ring

bool draws_focus_ring() const override

None: the field shows focus with its accent border.

struct CharacterFormat

A character format to apply to a range, or read back from one. Applying it sets every field that is set and leaves every unset one alone. When reading, a field is unset where the range mixes values.

control_default lists the color, family and size fields that follow the control's own value. For the background, that means no highlight. When applied, those fields go back to the control's value before the set fields are applied. When read, it lists the fields the whole range leaves to the control.

Example:

auto editor = std::make_unique<ui::RichEditBoxControl>("Hello, world!");
editor->select(0, 5);
editor->set_selection_format({.italic = true, .font_size = 20.0f}); // bold etc. untouched
if (editor->selection_format().bold == true) { /* ... */ }          // the whole range is bold
editor->set_selection_format({.control_default = ui::RichTextFormatField::Foreground});

Field Type Default
bold std::optional<bool> Bold on/off; unset leaves it, or reads as mixed.
italic std::optional<bool> Italic on/off.
underline std::optional<bool> Underline on/off.
strikethrough std::optional<bool> Strikethrough on/off.
foreground std::optional<Color> Text color.
background std::optional<Color> Highlight color.
font_family std::optional<std::string> Font family.
font_size OptionalFloat Font size in DIPs.
control_default RichTextFormatField RichTextFormatField::None The fields that follow the control's own value.

struct RichTextFormat

The character format one character of a RichEditBox carries. An unset color, family or size follows the control's own (its foreground(), font_family() and font_size()).

Field Type Default
bold bool false Bold weight.
italic bool false Italic style.
underline bool false Underlined.
strikethrough bool false Struck through.
foreground std::optional<Color> Text color; unset is the control's foreground().
background std::optional<Color> Highlight behind the text; unset paints none.
font_family std::optional<std::string> Font family; unset is the control's font_family().
font_size OptionalFloat Font size in DIPs; unset is the control's font_size().

struct RichTextSpan

A stretch of a RichEditBox's text in one format, as spans() hands out and set_spans() takes.

Example:

auto box = std::make_unique<ui::RichEditBoxControl>();
box->set_spans({
    {.text = "Hello, "},
    {.text = "world", .format = {.bold = true, .foreground = ui::Color::from_argb(0xFFC42B1C)}},
    {.text = "!\nA second paragraph."},
});

Field Type Default
text std::string The text (UTF-8); \n is a paragraph mark.
format RichTextFormat The format every character of text carries.

Enumerations

RichTextFormatField

enum class RichTextFormatField : std::uint8_t

The fields of a RichTextFormat that can follow the control's own value, as RichEditBoxControl::CharacterFormat::control_default lists them. Flags.

Value
None no field
Foreground the text color
Background the highlight (none)
FontFamily the font family
FontSize the font size
All every one of them

DisabledFormattingShortcuts

enum class DisabledFormattingShortcuts : std::uint8_t

The Ctrl+B/I/U shortcuts, with their context menu commands, to leave out. Flags.

Value
None every shortcut works (the default)
Bold no Ctrl+B / Bold
Italic no Ctrl+I / Italic
Underline no Ctrl+U / Underline
All none of them

Functions

operator|

RichTextFormatField operator|(RichTextFormatField a, RichTextFormatField b)

Combines RichTextFormatField flags.

operator&

RichTextFormatField operator&(RichTextFormatField a, RichTextFormatField b)

The flags both a and b have.

has_flag

bool has_flag(RichTextFormatField set, RichTextFormatField flag)

Whether set includes any bit of flag.

operator|

DisabledFormattingShortcuts operator|(DisabledFormattingShortcuts a, DisabledFormattingShortcuts b)

Combines DisabledFormattingShortcuts flags.

has_flag

bool has_flag(DisabledFormattingShortcuts set, DisabledFormattingShortcuts flag)

Whether set includes any bit of flag.

Tests

  • tests/test_rich_edit_box.cpp