Skip to content

Button

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

A push button, clicked by mouse (press and release inside), Enter/Space while focused, or UI Automation's Invoke.

Shows either a plain text() label, whose color tracks hover/press/disabled automatically, or arbitrary set_content() (an icon plus text, say). A click runs, in order: the attached flyout() toggles open or closed, the protected on_activated() hook, the on_click() handler, the command (set_command()), then navigate_uri() opens if set. The look comes from variant() and the theme, with per-instance background / foreground overrides. Not final: ToggleButton and DropdownButton derive from it through the protected hooks.

Example:

ui::Property<int> clicks{0};

auto       stack = std::make_unique<ui::StackControl>(ui::Orientation::Vertical);
auto*      label = stack->add(std::make_unique<ui::TextControl>("Clicks: 0"));
ui::ButtonControl* go = stack->add(std::make_unique<ui::ButtonControl>("Click me"));
go->set_variant(ui::ButtonVariant::Accent);
go->on_click([label, n = 0]() mutable { label->set_text(std::format("Clicks: {}", ++n)); });
window.set_content(std::move(stack));

// Declaratively:
window.set_content(ui::VerticalStack{
    .spacing  = 12,
    .content  = {
        ui::Text{.text = ui::bind(clicks, [](int n) { return std::format("Clicks: {}", n); })},
        ui::Button{.text = "Click me", .variant = ui::ButtonVariant::Accent,
                          .on_click = [&] { ++clicks; }},
        ui::Button{.text = "Docs", .navigate_uri = "https://example.com/docs",
                          .variant = ui::ButtonVariant::Hyperlink},
    },
});

ui::Button

A push button. Builds a ui::ButtonControl.

.text is the label; .content replaces it with any subtree (an icon beside text, say). A click runs .on_click, then .command, then opens .navigate_uri. With a .command the button is enabled only while both .enabled and the command's enabled allow it, and an unset .tool_tip falls back to the command's description.

Example:

ui::Property<int> clicks{0};
ui::Property<bool> dirty{false};
ui::Command       save{.enabled = dirty, .execute = [&] { save_document(); }};

window.set_content(ui::VerticalStack{
    .spacing  = 8,
    .content  = {
        ui::Button{.text = "Click me", .on_click = [&] { ++clicks; }},
        ui::Button{.text = "Save", .command = save}, // enablement comes with the command
        ui::Button{.variant = ui::ButtonVariant::Accent,
                   .content = ui::HorizontalStack{
                       .spacing  = 8,
                       .content  = {ui::Icon{.source = ui::glyphs::More}, ui::Text{.text = "More"}},
                   }},
        ui::Button{.text         = "Docs",
                   .navigate_uri = "https://example.com/docs",
                   .variant      = ui::ButtonVariant::Hyperlink},
    },
});

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

ui::Button{
    .text, .navigate_uri, .variant, .padding, .background, .foreground, .style, .margin,
    .width, .height, .min_width, .min_height, .max_width, .max_height, .horizontal_alignment,
    .vertical_alignment, .visible, .tool_tip, .enabled, .accessibility, .command,
    .command_parameter, .on_click, .on_context_requested, .on_pointer_entered,
    .on_pointer_exited, .on_got_focus, .on_lost_focus, .on_key_down, .on_wheel, .control,
    .content
}

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

Field Type Default
text ui::Binding<std::string> The label, ignored while .content is set.
navigate_uri ui::Binding<std::string> A URI (ui::ButtonControl::set_navigate_uri()), opened with its default handler on every click, after .on_click and .command.
variant ui::Binding<ui::ButtonVariant> {ui::ButtonVariant::Neutral} Neutral, Accent, Subtle or Hyperlink (ui::ButtonControl::set_variant()).
padding ui::Binding<std::optional<ui::Thickness>> The padding around the text or content; nullopt keeps the theme's button padding.
background ui::Binding<ui::Paint> The fill at rest (ui::ButtonControl::set_background()): a fixed color or a theme resource, which follows theme switches. Unset keeps the style's.
foreground ui::Binding<ui::Paint> The label color at rest: a fixed color or a theme resource, which follows theme switches. Unset keeps the style's.
command ui::CommandRef A shared, named action (ui/Command.hpp), run after .on_click with .command_parameter. The button is enabled only while both the command and .enabled allow it, and follows the command as it changes. Written as the command itself: .command = save.
command_parameter std::string Handed to .command when it runs.
on_click ui::Action A completed press and release inside, or Enter/Space while focused. Runs before .command.
on_context_requested ui::Callback<ui::Handling(std::optional<ui::Point>)> A context menu was requested (Element::on_context_requested()). Returning nothing, or ui::Handled, means a menu or flyout was shown; ui::NotHandled lets the request go on.
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::Button> Out: the handle that receives the built control.
content ui::Spec Arbitrary content instead of a plain label, such as an icon plus text. When set it replaces .text for layout and rendering entirely.

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

ui::ButtonControl

Derives from ui::Element, ui::InvokeProvider.

A push button, clicked by mouse (press and release inside), Enter/Space while focused, or UI Automation's Invoke.

Shows either a plain text() label, whose color tracks hover/press/disabled automatically, or arbitrary set_content() (an icon plus text, say). A click runs, in order: the attached flyout() toggles open or closed, the protected on_activated() hook, the on_click() handler, the command (set_command()), then navigate_uri() opens if set. The look comes from variant() and the theme, with per-instance background / foreground overrides. Not final: ToggleButton and DropdownButton derive from it through the protected hooks.

Example:

ui::Property<int> clicks{0};

auto       stack = std::make_unique<ui::StackControl>(ui::Orientation::Vertical);
auto*      label = stack->add(std::make_unique<ui::TextControl>("Clicks: 0"));
ui::ButtonControl* go = stack->add(std::make_unique<ui::ButtonControl>("Click me"));
go->set_variant(ui::ButtonVariant::Accent);
go->on_click([label, n = 0]() mutable { label->set_text(std::format("Clicks: {}", ++n)); });
window.set_content(std::move(stack));

// Declaratively:
window.set_content(ui::VerticalStack{
    .spacing  = 12,
    .content  = {
        ui::Text{.text = ui::bind(clicks, [](int n) { return std::format("Clicks: {}", n); })},
        ui::Button{.text = "Click me", .variant = ui::ButtonVariant::Accent,
                          .on_click = [&] { ++clicks; }},
        ui::Button{.text = "Docs", .navigate_uri = "https://example.com/docs",
                          .variant = ui::ButtonVariant::Hyperlink},
    },
});

ButtonControl

ButtonControl() = default

A button with an empty label.

ButtonControl

ButtonControl(std::string text)

A button labeled text (UTF-8).

text

const std::string& text() const

The plain-text label (UTF-8), drawn centered in the current state's text color, so it tracks hover/press/disabled automatically. Also the automation name fallback. Ignored once content() is set.

set_text

void set_text(std::string text)

Sets the label (UTF-8). The button is measured again and redrawn.

set_content

template <typename T>
T* set_content(std::unique_ptr<T> content)

Any Element as content in place of the text label, e.g. a StackControl of an icon and a TextControl. Takes ownership, destroys any previous content, and returns a non-owning observer, as StackControl::add() does. When set, content replaces text() for layout and rendering. The caller picks the content's colors, because the state's foreground color only applies to text(). content must not be null. The button is measured again and redrawn.

content

Element* content() const

The content set with set_content() as a non-owning observer, or nullptr for a text button.

variant

ButtonVariant variant() const

The token set the button draws with; ButtonVariant::Neutral by default.

set_variant

void set_variant(ButtonVariant variant)

Redraws only; every style has the same metrics.

set_padding

void set_padding(std::optional<Thickness> padding)

The padding around the text or content, DIPs; nullopt (the default) keeps the theme's button padding (11,5,11,6 in Fluent). The theme padding clips the icon of a small fixed-size icon button, such as a 40x40 title-bar back button, so pass Thickness there. The button is measured again and redrawn.

on_click

void on_click(Callback<void()> handler)

Invoked on a completed click (mouse press and release inside, Enter/Space while focused, or UI Automation's Invoke) while enabled, before the command runs. The handler may destroy the button. Replaces any previous handler.

set_flyout

void set_flyout(FlyoutBase* flyout)

A click opens flyout (and a second one closes it). Set for you when a Button becomes a Flyout's anchor; the Flyout owns the Button, so the pointer never outlives what it points at.

flyout

FlyoutBase* flyout() const

The flyout a click opens, or nullptr; owned by that flyout, not the button.

set_command

void set_command(CommandRef command, std::string parameter = {})

Runs command with parameter on every click, after the click handler. The button is enabled only while the command is, and follows it as it changes; without a tooltip of its own it shows the command's description. Either may go first: a destroyed command leaves the button as if it had none. An empty ref clears it. A plain Button takes no label or icon from the command.

command

const Command* command() const

The command set with set_command(), or nullptr (none, or destroyed); not owned.

command_parameter

const std::string& command_parameter() const

The parameter passed to the command's execute.

set_navigate_uri

void set_navigate_uri(std::string uri)

Every click opens uri in its default handler, after the click handler and the command. Meant for ButtonVariant::Hyperlink, but works with any variant. Empty (the default) opens nothing.

const std::string& navigate_uri() const

The URI a click opens, or empty.

parallel_measure_safe

bool parallel_measure_safe() const override

True for a text button. A button with content() answers false, because it measures the content without listing it in for_each_child() (the two share one tab stop).

focused

bool focused() const

Whether the button holds keyboard focus.

focusable

bool focusable() const override

A disabled button is skipped in the tab order.

set_background

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

The button's fill at rest. Hover, press and the disabled state still use the theme's fills. nullopt (the default) keeps the style's. Clears a theme-resource background. Redraws.

set_background

void set_background(ThemeColor resource)

The button's fill at rest as a theme resource, re-read under every theme. Clears a fixed background. Redraws.

set_foreground

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

The label color in the Normal state; nullopt (the default) keeps the style's. Clears a theme-resource foreground. Redraws.

set_foreground

void set_foreground(ThemeColor resource)

The label color in the Normal state as a theme resource, re-read under every theme. It wins over a fixed foreground color. Redraws.

handle_global_shortcut

bool handle_global_shortcut(const KeyEvent& e) override

The command's shortcut, app-wide: clicks the button while it is enabled. Nothing without a command or with an empty key combination.

tool_tip

std::string tool_tip() const override

The tooltip set on the button, else its command's description.

is_pressed

bool is_pressed() const

True while pressed by the pointer (inside) or by a held Enter/Space.

cursor_at

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

The Hand cursor for ButtonVariant::Hyperlink; no opinion otherwise.

automation_role

AutomationRole automation_role() const override

Hyperlink for ButtonVariant::Hyperlink, Button otherwise.

automation_name_fallback

std::string automation_name_fallback() const override

text(), used when Accessibility::name is not set. Empty for a content button: name it through set_accessibility().

style_visual

static ButtonVisual style_visual(const Theme& theme, ButtonVariant variant, ButtonState state)

variant's colors in state: the Neutral/Accent/Subtle/Hyperlink table every Button draws from, and every button-shaped part of another control (SplitButton's two halves).

visual_state

ButtonState visual_state() const

The state the button currently draws: Disabled, Pressed, Hover or Normal, derived from the enabled flag and the pointer/key input.

measure_override

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

Desired size = measured text (or content()'s own measure()) + padding, clamped to available; the padding is cached for arrange_override().

arrange_override

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

Places content() inside the padding measure_override() last used, centered.

pointer_override

bool pointer_override(const PointerEvent& e) override

Tracks hover and press; a release inside after a press inside is a click.

render_override

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

Emits: fill_rounded_rect (background) + draw_text (or the content), plus a stroke_rounded_rect_gradient elevation border for Neutral/Accent's Normal/Hover states in Fluent. Every other state/style/theme has no border command at all. A focus ring stroke is added only while focused and enabled.

tick_override

void tick_override(Duration delta) override

Forwards the tick to content(), so animated content (an AnimatedIcon, a ProgressRing) keeps moving inside a button.

on_activated

virtual void on_activated()

Runs on every completed click (pointer, Enter/Space, UI Automation's Invoke) before the click handler. ToggleButton flips its state here.

current_visual

virtual ButtonVisual current_visual(const Theme& theme) const

The colors the current state draws with; variant()'s set by default.

activate

void activate()

Performs a click as if the user completed one, when enabled: the flyout toggle, on_activated(), on_click, the command, then navigate_uri().

set_trailing_reserve

void set_trailing_reserve(float width)

Width kept free at the right of the content, inside the padding, for a trailing part the derived control draws itself: DropdownButton's chevron column. The content (or the label) is laid out and centered in what is left.

trailing_reserve

float trailing_reserve() const

The width set by set_trailing_reserve(), DIPs; 0 by default.

padding

const Thickness& padding() const

The padding measure_override() last used (padding override or the theme's).

focus_ring_corner_radius

float focus_ring_corner_radius(const Theme& theme) const override

The ring follows the button's own corner radius.

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. Losing focus cancels an Enter/Space press in progress. Redraws.

key_override

bool key_override(const KeyEvent& e) override

Enter or Space down presses the button, and up (while still focused) clicks it.

automation_providers

void automation_providers(AutomationProviders& out) override

Lists what a client can do with this node: set out's field for each pattern this control implements now to this, through the provider interface it inherits privately (ui/Automation.hpp). A pattern that only applies in some states is an if. out arrives empty, and nothing is listed by default.

Example:

class Chip final : public ui::Element, private ui::InvokeProvider
{
protected:
    void automation_providers(ui::AutomationProviders& out) override
    {
        out.invoke = this;
    }

private:
    bool invoke() override
    {
        on_click_();
        return true;
    }
    ui::Callback<void()> on_click_;
};

Enumerations

ButtonVariant

enum class ButtonVariant

Which of Theme's per-control token sets a Button draws with.

Example:

auto save = std::make_unique<ui::ButtonControl>("Save");
save->set_variant(ui::ButtonVariant::Accent); // the one high-emphasis action in the view

Value
Neutral The default look: a subtle translucent fill with an elevation border.
Accent The filled, high-emphasis look, in the accent color. Use at most one per view.
Subtle Borderless, showing only a wash on hover/press: toolbar, command-bar and menu buttons.
Hyperlink A hyperlink button: Subtle's backgrounds under link-colored text, a Hand cursor, reported to UI Automation as a hyperlink, and opening ButtonControl::navigate_uri() when clicked.

Example

window.set_content(ui::VerticalStack{
    .spacing              = 12,
    .horizontal_alignment = ui::Alignment::Center,
    .vertical_alignment   = ui::Alignment::Center,
    .content =
        {
            ui::Text{.text = ui::bind(clicks, [](int n) { return std::format("Clicks: {}", n); })},
            ui::Button{.text = "Click me", .on_click = [&] { ++clicks; }},
            ui::Button{.text = "Reset", .on_click = [&] { clicks = 0; }},
        },
});

Tests

  • tests/test_button.cpp