Skip to content

TextBox

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

Single- or multi-line editable text.

A focusable field with a blinking caret, mouse and keyboard selection, and Ctrl+C/X/V through the host's Clipboard. When the host provides a SpellChecker, misspellings get squiggles and a context menu of suggestions. It can show a header above the field and a placeholder while empty. set_accepts_return() or set_wrapping() make it multi-line. A multi-line box grows a line at a time and scrolls when it is shorter than its text. set_password_char() makes it PasswordBox's edit part. on_text_changed() reports user edits only, never set_text(). In a spec tree it is built by ui::TextBox.

Example:

ui::Property<std::string> name{"Ada"};
ui::Control<ui::TextBox> notes;

window.set_content(ui::VerticalStack{
    .spacing  = 12,
    .content  = {
        ui::TextBox{.header           = "Name",
                    .placeholder_text = "Your name",
                    .text             = name,
                    .on_submit        = [&] { /* save name.get() */ }},
        ui::TextBox{.header         = "Notes",
                    .accepts_return = true,
                    .wrapping       = ui::TextWrapping::Wrap,
                    .height         = 120,
                    .control        = notes},
        ui::Text{.text = ui::bind(name, [](const std::string& n) { return "Hello, " + n; })},
    },
});

// Or imperatively:
auto  stack = std::make_unique<ui::StackControl>(ui::Orientation::Vertical);
auto* code  = stack->add(std::make_unique<ui::TextBoxControl>());
code->set_header("Activation code");
code->set_max_length(8);
code->set_character_casing(ui::CharacterCasing::Upper);
code->set_spell_check(false);
code->on_text_changed([](const std::string& text) { /* validate */ });

ui::TextBox

An editable text field, single-line by default. Builds a ui::TextBoxControl.

.text = prop is two-way: the box shows the property, and what the user types is written back into it before .on_text_changed runs. A bind with a conversion, a const property and ui::bind_one_way() only display the value. .accepts_return or a .wrapping other than NoWrap make it multi-line.

Example:

ui::Property<std::string> name{"Ada"};

window.set_content(ui::VerticalStack{
    .spacing  = 8,
    .content  = {
        ui::TextBox{ .header           = "Name",
                     .placeholder_text = "Your name",
                     .text             = name,
                     .max_length       = 40,
                     .width            = 240,
                     .on_submit        = [] { save(); } },
        ui::Text{ .text = ui::bind(name, [](const std::string& n) { return "Hello, " + n; }) },
    },
});

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

ui::TextBox{
    .header, .description, .error, .placeholder_text, .text, .max_length, .spell_check,
    .read_only, .accepts_return, .accepts_tab, .character_casing, .wrapping, .font_family,
    .font_size, .font_weight, .italic, .character_spacing, .foreground,
    .selection_highlight_color, .unfocused_selection_highlight_color, .color_spans,
    .line_numbers, .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_edit, .on_submit, .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 text is empty.
text ui::TwoWayField<std::string> The edited string (UTF-8). ui::bind(prop) is two-way: an edit is written back into the property. Each new bound value replaces the whole text; the text the box already holds changes nothing, so the write-back keeps the caret.
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} Spell checking with squiggles, suggestions and autocorrect. Turn it off for fields that don't hold prose, such as codes, identifiers and user names.
read_only ui::Binding<bool> {false} Selectable and copyable, never edited.
accepts_return ui::Binding<bool> {false} Enter inserts a line break; the box becomes multi-line.
accepts_tab ui::Binding<bool> {false} Tab inserts a tab character instead of moving focus (TextBoxControl::set_accepts_tab()).
character_casing ui::Binding<ui::CharacterCasing> {ui::CharacterCasing::Normal} The casing applied to what the user types or pastes.
wrapping ui::Binding<ui::TextWrapping> {ui::TextWrapping::NoWrap} Anything but NoWrap breaks lines at the box's width (multi-line).
font_family ui::Binding<std::optional<std::string>> The font family; unset follows the theme.
font_size ui::Binding<std::optional<float>> The font size, DIPs; unset follows the theme.
font_weight ui::Binding<std::optional<ui::FontWeight>> The font weight; unset follows the theme.
italic ui::Binding<bool> {false} Italic text.
character_spacing ui::Binding<int> {0} Extra space between characters, in thousandths of an em.
foreground ui::Binding<ui::Paint> The text color: 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.
unfocused_selection_highlight_color ui::Binding<ui::Paint> The color behind selected text while the box doesn't have focus; empty (the default) hides the selection then (TextBoxControl::set_unfocused_selection_highlight_color()).
color_spans ui::Binding<std::vector<ui::TextColorSpan>> Colors for ranges of the text, as a syntax highlighter makes them (TextBoxControl::set_color_spans()).
line_numbers ui::Binding<bool> {false} A column of line numbers left of a multi-line box's text.
on_text_changed ui::Callback<void(const std::string&)> Fired after every user edit, with the new text. Handlers in a spec returned from a function must capture by value or capture state that outlives the window (see spec/Spec.hpp).
on_edit ui::Callback<void(const ui::TextBoxEditArgs&)> Every change to the text with what changed, for an incremental parser (TextBoxControl::on_edit()).
on_submit ui::Action Fired on Enter (unless .accepts_return makes Enter insert a line break).
on_selection_changed ui::Action The caret or the selection moved (TextBoxControl::on_selection_changed()).
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::TextBox> Out: the handle that receives the built control.

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::TextBoxControl; the caller owns it.

ui::TextBoxControl

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

Single- or multi-line editable text.

A focusable field with a blinking caret, mouse and keyboard selection, and Ctrl+C/X/V through the host's Clipboard. When the host provides a SpellChecker, misspellings get squiggles and a context menu of suggestions. It can show a header above the field and a placeholder while empty. set_accepts_return() or set_wrapping() make it multi-line. A multi-line box grows a line at a time and scrolls when it is shorter than its text. set_password_char() makes it PasswordBox's edit part. on_text_changed() reports user edits only, never set_text(). In a spec tree it is built by ui::TextBox.

Example:

ui::Property<std::string> name{"Ada"};
ui::Control<ui::TextBox> notes;

window.set_content(ui::VerticalStack{
    .spacing  = 12,
    .content  = {
        ui::TextBox{.header           = "Name",
                    .placeholder_text = "Your name",
                    .text             = name,
                    .on_submit        = [&] { /* save name.get() */ }},
        ui::TextBox{.header         = "Notes",
                    .accepts_return = true,
                    .wrapping       = ui::TextWrapping::Wrap,
                    .height         = 120,
                    .control        = notes},
        ui::Text{.text = ui::bind(name, [](const std::string& n) { return "Hello, " + n; })},
    },
});

// Or imperatively:
auto  stack = std::make_unique<ui::StackControl>(ui::Orientation::Vertical);
auto* code  = stack->add(std::make_unique<ui::TextBoxControl>());
code->set_header("Activation code");
code->set_max_length(8);
code->set_character_casing(ui::CharacterCasing::Upper);
code->set_spell_check(false);
code->on_text_changed([](const std::string& text) { /* validate */ });

MaxUndoSteps

static std::size_t MaxUndoSteps = 1000

How many edits undo() keeps; older ones drop out.

SpellCheckDelayMs

static double SpellCheckDelayMs = 250.0

Idle time in ms after the last edit before the whole text is checked again.

MaxSpellingSuggestions

static std::size_t MaxSpellingSuggestions = 5

The most spelling suggestions the context menu lists, best first.

CaretBlinkIntervalMs

static double CaretBlinkIntervalMs = 530.0

How long the caret stays shown, and then hidden, in ms. The user's blink setting is not read.

TextBoxControl

TextBoxControl()

An empty, enabled, single-line box.

TextBoxControl

TextBoxControl(std::string text)

A box holding text (UTF-8), the caret at its end.

text

const std::string& text() const

The edited string, UTF-8.

set_text

void set_text(std::string text)

Replaces the text and moves the caret to the end. Setting the same text again changes nothing, caret and selection included, so a two-way binding writing the value back is harmless. Doesn't fire on_text_changed(), which reports user edits only.

caret

std::size_t caret() const

The caret position, a byte offset into text().

selection_range

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

The selected range as [start, end) byte offsets; start == end means no selection.

has_selection

bool has_selection() const

Whether selection_range() is non-empty.

const std::string& header() const

The header text; empty when there is none.

set_header

void set_header(std::string header)

The label above the control. Empty (the default) draws none and takes no room.

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.

hovered

bool hovered() const

Whether the pointer is over the box, which draws it in its hover colors.

placeholder_text

const std::string& placeholder_text() const

The placeholder hint; empty (the default) draws none.

set_uses_theme_min_height

void set_uses_theme_min_height(bool uses)

Whether measure applies the theme's minimum text box height (32 by default). On for a TextBox of its own, off for one serving as another control's edit part (ComboBox, NumberBox), which sizes itself.

set_placeholder_text

void set_placeholder_text(std::string text)

A hint drawn in a secondary text color while text() is empty, focused or not. Never part of text(), never measured into the desired size.

set_chromeless

void set_chromeless(bool chromeless)

A chromeless box draws no background fill, border stroke or focus underline, only the text, selection and caret, so a host control (ComboBox) can paint one background and border under its whole surface.

font_family

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

The font family override; unset (the default) follows the theme.

set_font_family

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

Sets or clears the font family; unset follows the theme.

font_size

std::optional<float> font_size() const

The font size override in DIPs; unset (the default) follows the theme.

set_font_size

void set_font_size(OptionalFloat size)

Sets or clears the font size; unset follows the theme. The box grows with it.

font_weight

std::optional<FontWeight> font_weight() const

The font weight override; nullopt (the default) follows the theme.

set_font_weight

void set_font_weight(std::optional<FontWeight> weight)

Sets the font weight; nullopt follows the theme.

italic

bool italic() const

Whether the text is italic; false by default.

set_italic

void set_italic(bool italic)

Italic text.

character_spacing

int character_spacing() const

The extra space between characters in thousandths of an em; 0 by default.

set_character_spacing

void set_character_spacing(int spacing)

Sets the extra space between characters, in thousandths of an em.

foreground

const Paint& foreground() const

The text color: a fixed color or a theme resource; empty (the default) follows the theme.

set_foreground

void set_foreground(Paint color)

The text's color; unset follows the theme. The placeholder keeps its own.

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.

unfocused_selection_highlight_color

const Paint& unfocused_selection_highlight_color() const

The color behind selected text while the box doesn't have focus, as a fixed color or a theme resource. Empty (the default) hides the selection then.

set_unfocused_selection_highlight_color

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

Shows the selection in color while the box doesn't have focus, with the text over it unchanged. A find bar uses this to keep the match it selected visible. nullopt hides it again.

set_unfocused_selection_highlight_color

void set_unfocused_selection_highlight_color(ThemeColor resource)

The theme-resource form of set_unfocused_selection_highlight_color().

text_style

TextStyle text_style(const Theme& theme) const

The style the text is measured and drawn in: the theme's, under the font properties above.

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)

The text can be selected and copied but not changed, and no caret is drawn.

accepts_return

bool accepts_return() const

Whether Enter inserts a line break; false by default.

set_accepts_return

void set_accepts_return(bool accepts)

Enter inserts a line break instead of submitting, and pasted line breaks are kept. Makes the box multi-line.

wrapping

TextWrapping wrapping() const

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

set_wrapping

void set_wrapping(TextWrapping wrapping)

Anything but NoWrap breaks the text at the box's width and makes it multi-line.

multiline

bool multiline() const

Whether the text is laid out as lines, which accepts_return() or wrapping turns on.

accepts_tab

bool accepts_tab() const

Whether Tab inserts a tab character; false by default.

set_accepts_tab

void set_accepts_tab(bool accepts)

Tab inserts a tab character instead of moving focus, while the box is editable. Shift+Tab then does nothing. An on_key_down handler can take both keys to indent and outdent.

takes_tab_key

bool takes_tab_key() const override

While accepts_tab() and editable.

color_spans

const std::vector<TextColorSpan>& color_spans() const

Colors for ranges of the text; empty by default.

set_color_spans

void set_color_spans(std::vector<TextColorSpan> spans)

Draws ranges of the text in their own colors, as a syntax highlighter does. The selection still draws over them. Ranges are byte offsets. The spans are sorted and overlaps are cut. An edit moves the spans after it and stretches or cuts the ones it touches, so they stay on their text until the next call. Multi-line boxes only.

line_numbers

bool line_numbers() const

Whether a multi-line box numbers its lines; false by default.

set_line_numbers

void set_line_numbers(bool shown)

Shows a column of line numbers left of a multi-line box's text, one per line of the text (a wrapped line keeps one number).

max_length

std::size_t max_length() const

The most characters the user can enter, in code points; 0 (the default) is no limit.

set_max_length

void set_max_length(std::size_t length)

The most characters (code points) the user can type or paste; 0, the default, is no limit. A paste is cut to what fits. Text already in the box or set from code is left alone, even when it is longer.

character_casing

CharacterCasing character_casing() const

How typed text is cased; Normal (keep as typed) by default.

set_character_casing

void set_character_casing(CharacterCasing casing)

What the user types or pastes from now on is lower- or upper-cased. Text already in the box keeps its case.

set_delete_button_enabled

void set_delete_button_enabled(bool enabled)

Whether this box has a delete button at all. On for a TextBox of its own. A control that uses one as its edit part and shows no delete button (ComboBox) turns it off.

set_trailing_space

void set_trailing_space(float width)

Room kept free at the right edge, after the delete button, for a button the owning control draws there itself, such as PasswordBox's reveal button or AutoSuggestBox's query button. The text never runs under it.

trailing_space

float trailing_space() const

The width in DIPs kept free by set_trailing_space(); 0 by default.

cursor_at

std::optional<CursorShape> cursor_at(Point p) const override

An I-beam over the text (the padding included), an arrow over the delete button and any button a containing control keeps in the trailing space.

delete_button_visible

bool delete_button_visible() const

Whether the delete button is shown and clickable right now. It shows while the box is single-line, editable, non-empty, focused and wider than five ems, under a theme that has one (none of the desktop eras does), unless set_delete_button_enabled() turned it off.

delete_button_rect

Rect delete_button_rect() const

The delete button's rect (empty while it is hidden).

set_password_char

void set_password_char(std::optional<char32_t> mask)

Draws every character as mask, and turns off copying, cutting, spell checking and word selection, which a password must not offer. nullopt shows the text.

password_char

std::optional<char32_t> password_char() const

The mask character; nullopt (the default) when the text is shown.

on_text_changed

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

Fired after any user-driven edit (typing, Backspace, Delete, cut, paste, undo and redo) and after replace_selection(), undo() and redo(), with the new text. set_text() raises nothing.

on_edit

void on_edit(Callback<void(const TextBoxEditArgs&)> handler)

Fired for every change to the text, with what changed: typing, pasting, undo and redo, replace_selection() and set_text() too. Runs right after the change, before on_text_changed() and before the caret moves. Don't edit the box from it. Use it to feed an incremental parser.

on_submit

void on_submit(Callback<void()> handler)

Fired when the user presses Enter, unless accepts_return() makes Enter insert a line break.

on_selection_changed

void on_selection_changed(Callback<void()> handler)

Fired when the caret or the selection moved, by a key, the pointer, an edit or a call.

select

void select(std::size_t anchor, std::size_t caret)

Selects from anchor to caret (byte offsets; the caret goes to caret, so an anchor after it selects backwards). Both are clamped to the text and moved back to a character boundary. The view scrolls to the caret.

select_all

void select_all()

Selects the whole text.

selected_text

std::string selected_text() const

The selected text; empty with nothing selected.

replace_selection

void replace_selection(std::string_view text)

Replaces the selection with text, or inserts it at the caret, and puts the caret after it. An edit like a typed one: it can be undone and raises on_text_changed(). Neither read_only nor max_length stops it.

can_undo

bool can_undo() const

Whether there is an edit undo() can take back. Ctrl+Z does the same.

can_redo

bool can_redo() const

Whether there is an undone edit redo() can make again. Ctrl+Y and Ctrl+Shift+Z do the same.

undo

void undo()

Takes back the last edit and puts the caret and the selection back where they were before it; nothing when can_undo() is false. Typing is undone a word at a time, a run of Backspace or Delete presses at once.

redo

void redo()

Makes the last undone edit again; nothing when can_redo() is false.

clear_undo_history

void clear_undo_history()

Forgets every edit, so undo() has nothing to take back. set_text() does this too, so a loaded document starts with an empty history.

parallel_measure_safe

bool parallel_measure_safe() const override

True: measuring reads only the box's own state, the measurer and the theme.

focused

bool focused() const

Whether the box has keyboard focus (caret shown, blink running).

focusable

bool focusable() const override

Focusable while enabled (a read-only box too, for selecting and copying).

spell_check

bool spell_check() const

Whether spell checking is on; true by default. Without a SpellChecker from the host it does nothing either way.

set_spell_check

void set_spell_check(bool enabled)

Turns spell checking on or off; turning it off drops every squiggle at once.

spelling_errors

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

The current misspellings, sorted by start. Includes the word being typed, which gets no squiggle until it's finished.

sync_spell_checker

void sync_spell_checker(SpellChecker& checker) override

Keeps the host's SpellChecker and schedules a full check with it. The Window calls this as the box gains focus. The box doesn't own the checker.

handle_context_menu

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

Shows spelling suggestions, "Add to dictionary" and "Ignore" over a squiggle, then Cut, Copy, Paste and Select All, each enabled when it can act.

caret_visible

bool caret_visible() const

The current blink phase: whether a focused box draws its caret right now.

automation_role

AutomationRole automation_role() const override

Always 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 wide as the text or placeholder plus padding and buttons, at most available.width. As tall as its lines plus padding, at least the theme's minimum text box height while set_uses_theme_min_height() is on. The header and description add their own room.

arrange_override

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

Places the header and the field; a multi-line box re-flows its text to the new width.

pointer_override

bool pointer_override(const PointerEvent& e) override

Click to place the caret, drag / Shift+click to select, double-click a word, triple-click everything. The delete button clears the text.

wheel_override

bool wheel_override(const PointerEvent& e) override

A multi-line box taller than its view scrolls on the wheel, and shows a scroll bar.

tick_override

void tick_override(Duration delta) override

Toggles caret visibility every CaretBlinkIntervalMs while focused.

render_override

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

Background, border and focus underline (unless chromeless), header, placeholder or text, selection highlight, squiggles, caret and delete button.

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 the Window as focus moves; restarts the caret blink on a gain.

key_override

bool key_override(const KeyEvent& e) override

Caret movement and selection keys, Backspace/Delete, Enter (submit or line break), Ctrl+A/C/X/V, and Ctrl+Z right after an autocorrection.

text_override

bool text_override(const TextInputEvent& text_event) override

Inserts typed UTF-8 text at the caret (replacing the selection) and fires on_text_changed(); control characters are dropped, CharacterCasing and MaxLength apply.

draws_focus_ring

bool draws_focus_ring() const override

None: the field shows focus with its accent border.

struct TextBoxEditArgs

What a TextBox's on_edit gets: removed at position became inserted. Byte offsets into the text before the change; the views are valid during the call.

Field Type Default
position std::size_t 0 Where the change starts.
removed std::string_view The text that was there.
inserted std::string_view The text that is there now.

Tests

  • tests/test_text_box.cpp