Skip to content

RadioButtons

Header: winui/ui/input/RadioButtons.hpp. Spec: winui/ui/input/RadioButtons.hpp.

A headed group of mutually exclusive string options, as one element.

The control owns one RadioButton per item (RadioGroup coordinates loose buttons instead), lays them out column-major in up to max_columns() equal-sized columns, and selects by index. The group is a single tab stop; focus lands on the selected (or first enabled) item. Up/Down move to the previous/next enabled item and select it, Left/Right move between columns, Ctrl+arrow moves focus without selecting (Space then selects), and an arrow with nowhere to go inside the group moves focus out of it. ui::RadioButtons is the declarative form.

Example:

ui::Property<std::string> message{"Nothing selected"};

window.set_content(ui::VerticalStack{
    .spacing  = 12,
    .content  = {
        ui::RadioButtons{
            .header               = "Options:",
            .items                = {"Option 1", "Option 2", "Option 3"},
            .max_columns          = 2,
            .on_selection_changed = [&message](std::optional<std::size_t> index) {
                message = index ? std::format("You selected Option {}", *index + 1) : "";
            },
        },
        ui::Text{.text = message},
    },
});

// Imperatively:
auto colors = std::make_unique<ui::RadioButtonsControl>(
    std::vector<std::string>{"Red", "Green", "Blue"}, /*selected_index=*/0);
colors->set_header("Color");
colors->item(2)->set_enabled(false); // disable a single option
colors->on_selection_changed([](std::optional<std::size_t> index) { /* unset when cleared */ });

ui::RadioButtons

A headed group of mutually exclusive options, one tab stop, selected by index. Builds a ui::RadioButtonsControl.

The initial selection is in place before any handler is installed, so it raises nothing.

Example:

ui::Property<std::optional<std::size_t>> background{0};

window.set_content(ui::RadioButtons{
    .header               = "Background",
    .items                = {"Green", "Yellow", "White"},
    .selected_index       = background,
    .max_columns          = 3,
});

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

ui::RadioButtons{
    .header, .items, .selected_index, .max_columns, .style, .margin, .width, .height,
    .min_width, .min_height, .max_width, .max_height, .horizontal_alignment,
    .vertical_alignment, .visible, .tool_tip, .enabled, .accessibility, .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 group. Empty draws none.
items ui::Binding<std::vector<std::string>> One radio button per string, in order. Bound, a new list replaces the buttons and clears the selection.
selected_index ui::TwoWayField<std::optional<std::size_t>> Unset (the default) selects nothing.
max_columns ui::Binding<std::size_t> {1} The most columns (at least 1). Items fill a column top to bottom before starting the next.
on_selection_changed ui::Callback<void(std::optional<std::size_t>)> The newly selected index (unset when cleared), after a click, Space or 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::RadioButtons> 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::RadioButtonsControl; the caller owns it.

ui::RadioButtonsControl

Derives from ui::Element.

A headed group of mutually exclusive string options, as one element.

The control owns one RadioButton per item (RadioGroup coordinates loose buttons instead), lays them out column-major in up to max_columns() equal-sized columns, and selects by index. The group is a single tab stop; focus lands on the selected (or first enabled) item. Up/Down move to the previous/next enabled item and select it, Left/Right move between columns, Ctrl+arrow moves focus without selecting (Space then selects), and an arrow with nowhere to go inside the group moves focus out of it. ui::RadioButtons is the declarative form.

Example:

ui::Property<std::string> message{"Nothing selected"};

window.set_content(ui::VerticalStack{
    .spacing  = 12,
    .content  = {
        ui::RadioButtons{
            .header               = "Options:",
            .items                = {"Option 1", "Option 2", "Option 3"},
            .max_columns          = 2,
            .on_selection_changed = [&message](std::optional<std::size_t> index) {
                message = index ? std::format("You selected Option {}", *index + 1) : "";
            },
        },
        ui::Text{.text = message},
    },
});

// Imperatively:
auto colors = std::make_unique<ui::RadioButtonsControl>(
    std::vector<std::string>{"Red", "Green", "Blue"}, /*selected_index=*/0);
colors->set_header("Color");
colors->item(2)->set_enabled(false); // disable a single option
colors->on_selection_changed([](std::optional<std::size_t> index) { /* unset when cleared */ });

RadioButtonsControl

RadioButtonsControl() = default

An empty group with no header and nothing selected.

RadioButtonsControl

RadioButtonsControl(std::vector<std::string> items, std::optional<std::size_t> selected_index = std::nullopt)

One RadioButton per string, in order, with selected_index checked (unset for none; no callback fires).

const std::string& header() const

The header above the items; also the automation name when none is set.

set_header

void set_header(std::string header)

The label above the options. Empty draws none and reserves no room for one.

item_count

std::size_t item_count() const

The number of options.

item

RadioButtonControl* item(std::size_t index) const

The button for item index, owned by this control; nullptr past the end.

set_items

void set_items(std::vector<std::string> items)

Replaces every item and clears the selection (firing on_selection_changed if there was one). The new buttons take this control's enabled state.

selected_index

std::optional<std::size_t> selected_index() const

The checked item; unset when none is.

set_selected_index

void set_selected_index(std::optional<std::size_t> index)

Checks that item and unchecks the one before. Unset, or anything outside [0, item_count()), clears the selection. No-op, and no callback, when it does not change.

selected_item

std::string selected_item() const

The selected item's text, or an empty string.

on_selection_changed

void on_selection_changed(Callback<void(std::optional<std::size_t>)> handler)

Fired once per change with the new index (unset when cleared), whether a click, an arrow key, set_items() or set_selected_index() caused it. Replaces any previous handler.

max_columns

std::size_t max_columns() const

The most columns items are laid out in; 1 by default. Fewer are used when the offered width cannot hold them.

set_max_columns

void set_max_columns(std::size_t columns)

Clamped to >= 1.

focused

bool focused() const

Whether the group has keyboard focus.

focused_index

std::optional<std::size_t> focused_index() const

The item that has the keyboard's attention while the group is focused; unset otherwise.

focusable

bool focusable() const override

A tab stop while enabled with at least one enabled item.

for_each_child

void for_each_child(ChildVisitor visit) const override

The items, in order.

collect_focusables

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

Lists only the group, as one tab stop. The arrow keys move between the items.

automation_role

AutomationRole automation_role() const override

Group; each item is its own SelectionItem.

automation_name_fallback

std::string automation_name_fallback() const override

The header, when no Accessibility::name is set.

measure_override

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

The header plus a column-major grid of cells, each the size of the largest item, with 7 DIP column and 8 DIP row spacing.

arrange_override

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

Position children within the rect arrange() settled on (also in bounds(), window DIPs). Default: nothing, for a leaf.

pointer_override

bool pointer_override(const PointerEvent& e) override

Routes clicks to the owned buttons.

render_override

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

Draws the header and every item.

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

Keyboard focus arrived (true) or left (false); is_focused() already says so. The element is redrawn either way. No-op by default.

key_override

bool key_override(const KeyEvent& e) override

Arrow keys move the focused item (and select it unless Ctrl is held); Space selects it.

focus_ring_rect

Rect focus_ring_rect(const Theme& theme) const override

Around the focused item, as that item would ring itself.

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.

Tests

  • tests/test_radio_buttons.cpp