Skip to content

RadioButton

Headers: winui/ui/input/RadioButton.hpp, winui/ui/input/RadioGroup.hpp. Spec: winui/ui/input/RadioButton.hpp.

A round option button with a label, one of a set of mutually exclusive choices.

Clicking it (or Space while focused) checks it; clicking an already-checked RadioButton does nothing, so the user can turn a lone button on but never off. The button doesn't uncheck the others by itself. Register the buttons with a RadioGroup, which doesn't own them, or use RadioButtons, which owns and lays out a whole set. ui::RadioButton is the declarative form of a single button.

Example:

auto stack = std::make_unique<ui::StackControl>(ui::Orientation::Vertical);
ui::RadioButtonControl* small = stack->add(std::make_unique<ui::RadioButtonControl>("Small", true));
ui::RadioButtonControl* large = stack->add(std::make_unique<ui::RadioButtonControl>("Large"));

ui::RadioGroup group;
group.add(small);        // already checked: becomes the initial selection
group.add(large);
group.on_change([](ui::RadioButtonControl* chosen) { /* chosen->text() */ });
large->on_checked_changed([](bool checked) { /* fires for checks and unchecks */ });
window.set_content(std::move(stack));

ui::RadioButton

One radio button. Buttons that choose one value of a property are bound with ui::selects(): checking one sets the property, the others follow it, and the buttons act as one group for the keyboard (one Tab stop, arrow keys between them) wherever they sit in the tree. For a plain list of options, ui::RadioButtons is the whole group in one spec; imperative code can still tie loose buttons together with a RadioGroup.

Example:

enum class Size { Small, Large };
ui::Property<Size> size{Size::Small};

window.set_content(ui::VerticalStack{.content = {
    ui::RadioButton{.text = "Small", .checked = ui::selects(size, Size::Small)},
    ui::RadioButton{.text = "Large", .checked = ui::selects(size, Size::Large)},
}});

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

ui::RadioButton{
    .text, .checked, .style, .margin, .width, .height, .min_width, .min_height, .max_width,
    .max_height, .horizontal_alignment, .vertical_alignment, .visible, .tool_tip, .enabled,
    .accessibility, .on_checked_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
text ui::Binding<std::string> The label right of the dot.
checked ui::TwoWayField<bool> {false} Whether the button is selected; seeding it raises nothing. ui::selects() makes it one choice of a property's values and groups it with the other buttons bound to that property.
on_checked_changed ui::Callback<void(bool)> The new checked state, whenever it changes (a click, the group, a bound value).
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::RadioButton> 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::RadioButtonControl; the caller owns it.

ui::RadioButtonControl

Derives from ui::Element, ui::SelectionItemProvider.

A round option button with a label, one of a set of mutually exclusive choices.

Clicking it (or Space while focused) checks it; clicking an already-checked RadioButton does nothing, so the user can turn a lone button on but never off. The button doesn't uncheck the others by itself. Register the buttons with a RadioGroup, which doesn't own them, or use RadioButtons, which owns and lays out a whole set. ui::RadioButton is the declarative form of a single button.

Example:

auto stack = std::make_unique<ui::StackControl>(ui::Orientation::Vertical);
ui::RadioButtonControl* small = stack->add(std::make_unique<ui::RadioButtonControl>("Small", true));
ui::RadioButtonControl* large = stack->add(std::make_unique<ui::RadioButtonControl>("Large"));

ui::RadioGroup group;
group.add(small);        // already checked: becomes the initial selection
group.add(large);
group.on_change([](ui::RadioButtonControl* chosen) { /* chosen->text() */ });
large->on_checked_changed([](bool checked) { /* fires for checks and unchecks */ });
window.set_content(std::move(stack));

RadioButtonControl

RadioButtonControl() = default

An unchecked, enabled button with no label.

RadioButtonControl

RadioButtonControl(std::string text, bool checked = false)

A button labeled text (UTF-8). Seeding checked fires nothing, since no handler is attached yet.

text

const std::string& text() const

The label right of the dot; also the automation name when none is set.

set_text

void set_text(std::string text)

Sets the label. The button is measured again and redrawn.

checked

bool checked() const

Whether the button is selected; false by default.

set_checked

void set_checked(bool checked)

Sets the checked flag without unchecking the other buttons of a RadioGroup. RadioGroup::select() calls it to update every member. Call it yourself only on a standalone RadioButton with no group.

on_checked_changed

void on_checked_changed(Callback<void(bool)> handler)

Invoked with the new state whenever checked changes. Replaces any previous handler.

parallel_measure_safe

bool parallel_measure_safe() const override

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

focused

bool focused() const

Whether the button has keyboard focus.

focusable

bool focusable() const override

Whether this element can take keyboard focus (a leaf control opts in).

is_pressed

bool is_pressed() const

Whether a press is in progress: the pointer held down over the button, or Space held.

automation_role

AutomationRole automation_role() const override

Exposed to UI Automation as a RadioButton with a SelectionItem pattern.

automation_name_fallback

std::string automation_name_fallback() const override

automation_name()'s fallback when accessibility().name is empty. Default: no inherent name, correct for controls with no visible text of their own (Slider, ProgressBar), which need an explicit accessibility().name from the caller. A control with its own visible text (Button, CheckBox, TextControl, ...) overrides this to return it.

selection_group

const void* selection_group() const override

The property this button was bound to with ui::selects(), which groups it with the other buttons bound to it (Element::selection_group()); nullptr otherwise.

visual_state

ButtonState visual_state() const

The state the button is drawn in: Disabled, Pressed, Hover or Normal, in that priority.

measure_override

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

The dot plus the measured label.

pointer_override

bool pointer_override(const PointerEvent& e) override

A left click released over the button checks it (through its group, if any).

render_override

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

Draws the ring, the checked dot and the label.

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

Losing focus cancels a Space press in progress.

key_override

bool key_override(const KeyEvent& e) override

Space pressed and released while focused checks the button, like a click.

draws_focus_ring

bool draws_focus_ring() const override

Not while a RadioButtons owns it: the group rings it.

focus_ring_rect

Rect focus_ring_rect(const Theme& theme) const override

Around dot and label, or the dot alone in Metro's era.

focus_ring_corner_radius

float focus_ring_corner_radius(const Theme& theme) const override

The ring's corner radius: the theme's control radius by default.

class RadioGroup

Makes a set of loose RadioButtons mutually exclusive. Not an element: it holds non-owning observers of buttons owned elsewhere in the tree.

Every registered button that is clicked (or activated with Space or UI Automation) is routed through select(), which checks it and unchecks the rest. The group and its buttons may be destroyed in either order: a button that goes leaves the group, and a group that goes leaves its buttons plain radio buttons. For a headed, laid-out group selected by index, use RadioButtons instead.

Example:

ui::RadioGroup group;

auto stack = std::make_unique<ui::StackControl>(ui::Orientation::Vertical);
ui::RadioButtonControl* a = stack->add(std::make_unique<ui::RadioButtonControl>("A", true));
ui::RadioButtonControl* b = stack->add(std::make_unique<ui::RadioButtonControl>("B"));
group.add(a); // already checked: the initial selection
group.add(b);
group.on_change([](ui::RadioButtonControl* now) { /* now->text() */ });
group.select(b); // programmatic: checks B, unchecks A, fires on_change once
window.set_content(std::move(stack));

RadioGroup

RadioGroup() = default

A group with no members.

~RadioGroup

~RadioGroup()

Lets go of every member; the buttons stay as they are.

RadioGroup

RadioGroup(const RadioGroup&) = delete

Not copyable or movable: its buttons point at it.

operator=

RadioGroup& operator=(const RadioGroup&) = delete

Not copyable or movable: its buttons point at it.

add

RadioButtonControl* add(RadioButtonControl* button)

Registers button (a non-owning observer) with this group. If the button is already checked and no member has been selected yet, it becomes the initial selection. Otherwise the selection waits for select() or a click. A button belongs to one group: adding it takes it out of the one it was in.

remove

bool remove(RadioButtonControl* button)

Takes button out of the group, keeping its checked state. False when it is not a member. A member that is destroyed leaves by itself.

select

void select(RadioButtonControl* selected)

Checks selected and unchecks every other member, then fires on_change() once. Does nothing if selected is already the current selection, so clicking the checked button again changes nothing. A click on a member calls it, and an app may call it to select a member from code.

selected

RadioButtonControl* selected() const

The current selection (non-owning), or nullptr before anything was selected.

on_change

void on_change(Callback<void(RadioButtonControl*)> handler)

Fired once per selection change with the newly selected button, after every member's checked state is updated. Replaces any previous handler.

Tests

  • tests/test_radio_button.cpp