Skip to content

NumberBox

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

A text box for a number, validated and formatted when committed. It can have spin buttons, arithmetic input, stepping by keyboard and wheel, and a header.

value() is the number that counts. The user can type anything, and the text is committed on Enter, on losing focus and before each step. Text that parses is clamped to [min, max] and formatted again. Text that doesn't is replaced by the last value's text. An unset value (the default) means no number, and the box is empty and shows the placeholder. The box is one focus stop and owns its inner TextBox. In a spec tree it is built by ui::NumberBox.

Example:

auto stack = std::make_unique<ui::StackControl>(ui::Orientation::Vertical);
double quantity = 5.0;

auto box = std::make_unique<ui::NumberBoxControl>(/*value=*/5.0, /*min=*/0.0, /*max=*/100.0);
box->set_header("Quantity");
box->set_spin_button_placement(ui::NumberBoxSpinButtonPlacementMode::Inline);
box->set_step(1.0);
box->set_large_step(10.0);
box->set_accepts_expression(true); // "2 * 3" commits 6
box->set_number_formatter(ui::DecimalFormatter{.fraction_digits = 2});
box->on_value_changed([&](std::optional<double> v) { quantity = v.value_or(0.0); });
ui::NumberBoxControl* quantity_box = stack->add(std::move(box));

// Or declaratively:
ui::Property<std::optional<double>> price{9.5};
stack->add(ui::NumberBox{
    .header                = "Price",
    .placeholder_text      = "0.00",
    .value                 = price,
    .min                   = 0.0,
    .spin_button_placement = ui::NumberBoxSpinButtonPlacementMode::Compact,
}.build());

ui::NumberBox

A numeric field, with spin buttons when asked for. Builds a ui::NumberBoxControl.

Typed text is committed on Enter or when focus leaves. It is parsed, or evaluated with .accepts_expression, clamped to [min, max] and reported through .on_value_changed. A bound value outside [min, max] is clamped too, and a two-way property gets the clamped value written back. The arrow keys, the wheel and the spin buttons step by .step.

Example:

ui::Property<std::optional<double>> quantity{1.0};

window.set_content(ui::NumberBox{
    .header                = "Quantity",
    .value                 = quantity,
    .min                   = 0.0,
    .max                   = 99.0,
    .spin_button_placement = ui::NumberBoxSpinButtonPlacementMode::Inline,
    .accepts_expression    = true,
});

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

ui::NumberBox{
    .header, .description, .error, .placeholder_text, .value, .min, .max, .step, .large_step,
    .spin_button_placement, .accepts_expression, .number_formatter, .validation_mode,
    .wrap_enabled, .style, .margin, .width, .height, .min_width, .min_height, .max_width,
    .max_height, .horizontal_alignment, .vertical_alignment, .visible, .tool_tip, .enabled,
    .accessibility, .on_value_changed, .on_text_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 box is empty.
value ui::TwoWayField<std::optional<double>> Unset (the default) is no number: an empty box showing the placeholder.
min double -std::numeric_limits<double>::infinity() Minimum; committed values below it are clamped. Unbounded by default.
max double std::numeric_limits<double>::infinity() Maximum; committed values above it are clamped. Unbounded by default.
step ui::Binding<double> {1.0} What the arrow keys and spin buttons step by; 0 disables stepping.
large_step ui::Binding<double> {10.0} What PageUp and PageDown step by.
spin_button_placement ui::Binding<ui::NumberBoxSpinButtonPlacementMode> {ui::NumberBoxSpinButtonPlacementMode::Hidden} Where the spin buttons show; Hidden by default.
accepts_expression ui::Binding<bool> {false} Typed arithmetic is evaluated.
number_formatter ui::Binding<std::optional<ui::DecimalFormatter>> Formats the value and parses the text. Unset keeps the default: ten significant digits, no padding.
validation_mode ui::Binding<ui::NumberBoxValidationMode> {ui::NumberBoxValidationMode::InvalidInputOverwritten} What happens to input that does not parse. By default it is overwritten with the last valid value.
wrap_enabled ui::Binding<bool> {false} When true, a step past max lands on min, and a step past min lands on max.
on_value_changed ui::Callback<void(std::optional<double>)> Fires whenever the value changes, from a committed edit, a spin button or an arrow key step.
on_text_changed ui::Callback<void(const std::string&)> Fires on every user edit with the raw, not yet parsed text.
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::NumberBox> 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::NumberBoxControl; the caller owns it.

ui::NumberBoxControl

Derives from ui::Element, ui::RangeValueProvider.

A text box for a number, validated and formatted when committed. It can have spin buttons, arithmetic input, stepping by keyboard and wheel, and a header.

value() is the number that counts. The user can type anything, and the text is committed on Enter, on losing focus and before each step. Text that parses is clamped to [min, max] and formatted again. Text that doesn't is replaced by the last value's text. An unset value (the default) means no number, and the box is empty and shows the placeholder. The box is one focus stop and owns its inner TextBox. In a spec tree it is built by ui::NumberBox.

Example:

auto stack = std::make_unique<ui::StackControl>(ui::Orientation::Vertical);
double quantity = 5.0;

auto box = std::make_unique<ui::NumberBoxControl>(/*value=*/5.0, /*min=*/0.0, /*max=*/100.0);
box->set_header("Quantity");
box->set_spin_button_placement(ui::NumberBoxSpinButtonPlacementMode::Inline);
box->set_step(1.0);
box->set_large_step(10.0);
box->set_accepts_expression(true); // "2 * 3" commits 6
box->set_number_formatter(ui::DecimalFormatter{.fraction_digits = 2});
box->on_value_changed([&](std::optional<double> v) { quantity = v.value_or(0.0); });
ui::NumberBoxControl* quantity_box = stack->add(std::move(box));

// Or declaratively:
ui::Property<std::optional<double>> price{9.5};
stack->add(ui::NumberBox{
    .header                = "Price",
    .placeholder_text      = "0.00",
    .value                 = price,
    .min                   = 0.0,
    .spin_button_placement = ui::NumberBoxSpinButtonPlacementMode::Compact,
}.build());

NumberBoxControl

NumberBoxControl(std::optional<double> value = std::nullopt, double min = -std::numeric_limits<double>::infinity(), double max = std::numeric_limits<double>::infinity())

value unset is an empty box. The range is unbounded by default; max below min is raised to min (see set_range()).

~NumberBoxControl

~NumberBoxControl() override

NumberBoxControl

NumberBoxControl(const NumberBoxControl&) = delete

operator=

NumberBoxControl& operator=(const NumberBoxControl&) = delete

min

double min() const

Minimum; committed and stepped values are clamped to it. -infinity by default.

max

double max() const

Maximum; committed and stepped values are clamped to it. +infinity by default.

set_range

void set_range(double min, double max)

Sets the range and clamps the current value into it, as SliderControl::set_range() does. Fires on_value_changed() when that changes the value. A max below min becomes min.

value

std::optional<double> value() const

The committed number, unset for none. Not what is being typed until it commits.

set_value

void set_value(std::optional<double> value)

Sets the value, clamped to [min, max] under InvalidInputOverwritten, and formats the text to match. Fires on_value_changed() only when the value changed. Unset or NaN means no number, and the box is empty.

step

double step() const

What the arrow keys and spin buttons step by; 1 by default.

set_step

void set_step(double step)

Sets how far a spin button click or an Up or Down key press moves the value. Zero turns both off. Negative values count as 0.

large_step

double large_step() const

What PageUp/PageDown step by. 10 by default.

set_large_step

void set_large_step(double step)

Sets what PageUp and PageDown step by. Negative values count as 0.

spin_button_placement

NumberBoxSpinButtonPlacementMode spin_button_placement() const

Where the spin buttons show; Hidden by default.

set_spin_button_placement

void set_spin_button_placement(NumberBoxSpinButtonPlacementMode placement)

Sets where the spin buttons show: Hidden, Compact or Inline.

accepts_expression

bool accepts_expression() const

Whether committed text is evaluated as arithmetic ("1 + 2^2" is 5). False by default.

set_accepts_expression

void set_accepts_expression(bool accepts)

Committed text is evaluated as arithmetic.

number_formatter

const DecimalFormatter& number_formatter() const

The formatter the value is shown and typed text parsed with. The default shows up to ten significant digits with the user's regional separators.

set_number_formatter

void set_number_formatter(DecimalFormatter formatter)

Sets the formatter the value is shown and the text parsed with. Formats the text again at once.

validation_mode

NumberBoxValidationMode validation_mode() const

What happens to input that does not parse; InvalidInputOverwritten by default.

set_validation_mode

void set_validation_mode(NumberBoxValidationMode mode)

Sets what happens to input that does not parse. Takes effect from the next commit or step, and the current value is not validated again.

wrap_enabled

bool wrap_enabled() const

Whether stepping wraps around the range. False by default.

set_wrap_enabled

void set_wrap_enabled(bool wrap)

When true, a step past max lands on min, and a step past min lands on max.

placeholder_text

const std::string& placeholder_text() const

The inner TextBox's placeholder; empty by default.

set_placeholder_text

void set_placeholder_text(std::string text)

The hint shown while the box is empty (the value unset).

text

const std::string& text() const

The text as typed, not validated. It can differ from value until it is committed, on Enter, on losing focus, or before a spin button or arrow key step. set_text() does not fire on_text_changed().

set_text

void set_text(std::string text)
const std::string& header() const

The header text; empty by default.

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.

on_text_changed

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

Fired after every user edit of the text, such as typing, Backspace or Delete, with the text. on_value_changed() fires only once a parsed, clamped value changes.

on_value_changed

void on_value_changed(Callback<void(std::optional<double>)> handler)

Fired whenever value changes, from a committed text edit, a spin button click or an arrow key step.

focused

bool focused() const

Whether the box holds keyboard focus. The inner TextBox shows the caret then.

caret_bounds

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

The inner text box's caret.

focusable

bool focusable() const override

A tab stop while enabled.

for_each_child

void for_each_child(ChildVisitor visit) const override

Visits the internal TextBox.

collect_focusables

void collect_focusables(std::vector<Element*>& out) override

One tab stop: the internal TextBox is part of this control, not a stop of its own.

automation_children

void automation_children(std::vector<Element*>&) const override

One automation node: the internal TextBox is not listed as a child.

handle_context_menu

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

Shows Cut, Copy, Paste and Select All, with no spelling suggestions.

cursor_at

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

The text field's cursor over the text field: an I-beam, or an arrow over its buttons.

automation_role

AutomationRole automation_role() const override

AutomationRole::SpinButton.

up_visual_state

ButtonState up_visual_state() const

The inline up button's state: Disabled at the top of the range (unless wrapping), with no value, or while the box is disabled; else Pressed/Hover/Normal from the pointer.

down_visual_state

ButtonState down_visual_state() const

The inline down button's state, as up_visual_state() at the bottom of the range.

measure_override

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

Desired width is the internal TextBox's own desired width plus the spinner column; desired height is the TextBox's own single-line height.

arrange_override

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

Places the header, the inner TextBox and the spin-button columns (or Compact's indicator).

pointer_override

bool pointer_override(const PointerEvent& e) override

Spin-button presses step (and repeat while held); everything else goes to the TextBox.

covers_children_at

bool covers_children_at(Point p) const override

Whether a part of this element drawn over its children is at p (a scroll bar over the content), so the element is hit there instead of a child. Default: false.

wheel_override

bool wheel_override(const PointerEvent& e) override

The wheel steps the value while the box has focus. It then doesn't also scroll whatever holds the box.

render_override

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

Draws the header, the inner TextBox, and the inline spin buttons or Compact's indicator.

tick_override

void tick_override(Duration delta) override

Ticks the inner TextBox, then advances the spin-button press-and-hold repeat countdown while a button is held.

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

Focus reaches the inner TextBox; losing it commits the typed text.

key_override

bool key_override(const KeyEvent& e) override

Up/Down step by step(), PageUp/PageDown by large_step(), Enter commits; the other keys edit the text.

text_override

bool text_override(const TextInputEvent& text_event) override

Typed characters go to the inner TextBox; they are parsed only when committed.

draws_focus_ring

bool draws_focus_ring() const override

None: the field shows focus with its accent border.

Enumerations

NumberBoxSpinButtonPlacementMode

enum class NumberBoxSpinButtonPlacementMode

Where a ui::NumberBox shows its spin buttons.

Value
Hidden No spin buttons (the default); the keyboard and the wheel still step.
Compact A small indicator in the box; while it has focus, an up/down button pair floats beside it. Aero, Luna and the classic themes have no such popup and show the Inline buttons instead.
Inline Up and down buttons side by side at the box's right edge; the box is at least 120 DIPs wide. Aero stacks them in one narrow column inside the field.

NumberBoxValidationMode

enum class NumberBoxValidationMode

What a ui::NumberBox does with input that is not a number.

Value
InvalidInputOverwritten The default: text that does not parse is replaced by the value's text, and committed values are clamped to [min, max].
Disabled No validation: out-of-range values stand and unparsable text is left as typed.

Tests

  • tests/test_number_box.cpp