Skip to content

DropdownButton

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

A Button with a trailing chevron that says a click opens a menu or flyout.

It adds no behavior of its own to Button: the flyout is whichever FlyoutBase (a MenuFlyout, a Flyout) has it as its anchor, and a click opens that flyout as it would for any button. It adds a 12x12 animated chevron 8 DIPs right of the content, which dips on a press, and the ExpandCollapse automation pattern, expanded while the flyout is open. All of Button's API (text, content, style, on_click, command) applies.

Example:

// Declarative: the flyout owns its anchor.
window.set_content(ui::MenuFlyout{
    .items     = {{.text = "Send"}, {.text = "Reply"}, {.text = "Reply All", .on_click = [] { /* ... */ }}},
    .placement = ui::PopupPlacement::Below,
    .placement_alignment = ui::PopupPlacementAlignment::Leading,
    .anchor              = ui::DropdownButton{.text = "Email"},
});

// Imperative: the same tree built by hand.
auto  stack = std::make_unique<ui::StackControl>(ui::Orientation::Vertical);
auto* menu  = stack->add(std::make_unique<ui::MenuFlyoutControl>());
menu->set_items({{.text = "Send"}, {.text = "Reply"}, {.text = "Reply All"}});
menu->set_placement(ui::PopupPlacement::Below);
menu->set_placement_alignment(ui::PopupPlacementAlignment::Leading);
ui::DropdownButtonControl* email = menu->set_anchor(std::make_unique<ui::DropdownButtonControl>("Email"));

ui::DropdownButton

A button with a chevron that says it opens a flyout. Builds a ui::DropdownButtonControl.

The button does not hold the flyout itself: it is the .anchor of a ui::MenuFlyout or ui::Flyout, and a click opens that.

Example:

window.set_content(ui::MenuFlyout{
    .items     = {{.text = "Send"}, {.text = "Reply"}, {.text = "Reply All"}},
    .placement = ui::PopupPlacement::Below,
    .placement_alignment = ui::PopupPlacementAlignment::Leading,
    .anchor              = ui::DropdownButton{.text = "Email"},
});

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

ui::DropdownButton{
    .text, .variant, .padding, .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_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 before the chevron, ignored while .content is set.
variant ui::Binding<ui::ButtonVariant> {ui::ButtonVariant::Neutral} The button look, as ui::Button's.
padding ui::Binding<std::optional<ui::Thickness>> Padding around the label or content; nullopt keeps the theme's button padding.
command ui::CommandRef As ui::Button's: run after .on_click, and the button enabled only while it is.
command_parameter std::string Handed to .command when it runs.
on_click ui::Action A click; .command runs after it.
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::DropdownButton> Out: the handle that receives the built control.
content ui::Spec Arbitrary content (an icon, say) before the chevron, instead of .text.

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

ui::DropdownButtonControl

Derives from ui::ButtonControl, ui::ExpandCollapseProvider.

A Button with a trailing chevron that says a click opens a menu or flyout.

It adds no behavior of its own to Button: the flyout is whichever FlyoutBase (a MenuFlyout, a Flyout) has it as its anchor, and a click opens that flyout as it would for any button. It adds a 12x12 animated chevron 8 DIPs right of the content, which dips on a press, and the ExpandCollapse automation pattern, expanded while the flyout is open. All of Button's API (text, content, style, on_click, command) applies.

Example:

// Declarative: the flyout owns its anchor.
window.set_content(ui::MenuFlyout{
    .items     = {{.text = "Send"}, {.text = "Reply"}, {.text = "Reply All", .on_click = [] { /* ... */ }}},
    .placement = ui::PopupPlacement::Below,
    .placement_alignment = ui::PopupPlacementAlignment::Leading,
    .anchor              = ui::DropdownButton{.text = "Email"},
});

// Imperative: the same tree built by hand.
auto  stack = std::make_unique<ui::StackControl>(ui::Orientation::Vertical);
auto* menu  = stack->add(std::make_unique<ui::MenuFlyoutControl>());
menu->set_items({{.text = "Send"}, {.text = "Reply"}, {.text = "Reply All"}});
menu->set_placement(ui::PopupPlacement::Below);
menu->set_placement_alignment(ui::PopupPlacementAlignment::Leading);
ui::DropdownButtonControl* email = menu->set_anchor(std::make_unique<ui::DropdownButtonControl>("Email"));

DropdownButtonControl()

A button with no text; give it content through Button's setters.

DropdownButtonControl(std::string text)

A button labeled text, with the chevron after it.

~DropdownButtonControl() override

is_flyout_open

bool is_flyout_open() const

Whether the flyout the button opens is showing. False when no flyout has this button as its anchor.

chevron

AnimatedIconControl* chevron() const

The chevron (an AnimatedIcon), for an app that wants another source. Owned by the button. Its state ("Normal", "PointerOver" or "Pressed") follows the button's.

parallel_measure_safe

bool parallel_measure_safe() const override

The same answer as ButtonControl's, since the chevron measures only its glyph.

measure_override

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

Button's size, at least as tall as the chevron plus the padding.

arrange_override

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

Places the content as Button does and the chevron at the right edge of the padded area, centered vertically.

render_override

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

Draws the button, then the chevron in the theme's secondary text color at rest and its tertiary one under the pointer and while pressed. Disabled, on an Accent button and in themes without the Fluent palette, the chevron takes the label's color.

pointer_override

bool pointer_override(const PointerEvent& e) override

Button's pointer handling, then moves the chevron to the new visual state.

tick_override

void tick_override(Duration delta) override

Advances Button's animations and the chevron's, catching state changes without an event (set_enabled(), focus loss).

key_override

bool key_override(const KeyEvent& e) override

Button's keys (Enter or Space click on key up), then moves the chevron to the new state.

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_;
};

Tests

  • tests/test_split_button.cpp