MenuControl

June 6, 2026 · View on GitHub

Full-featured menu control supporting horizontal (menu bar) and vertical (sidebar) orientations, unlimited submenu nesting, keyboard and mouse navigation, and overlay-rendered dropdowns.

Overview

MenuControl renders either a horizontal menu bar (File, Edit, View) or a vertical sidebar of menu items. Each top-level item can have an unlimited hierarchy of child items, separators, keyboard-shortcut hints, and disabled states. Dropdowns and submenus are rendered as portal overlays that float above other content and are automatically positioned and clamped to the available screen space (opening below/above for horizontal bars, right/left for submenus).

Items are modeled by the MenuItem class, which supports display text, a (display-only) shortcut hint, a per-item foreground color, an Action to invoke on selection, a Tag for user data, and a live Children collection for nested submenus. Because MenuItem implements INotifyPropertyChanged, display properties such as Text, Shortcut, ForegroundColor, and IsEnabled can be data-bound from a view model.

Navigation is fully keyboard- and mouse-driven: arrow keys move between items and open/close submenus, letter keys jump to matching items, hover opens submenus after a short aim delay, and clicking outside or deactivating the window dismisses open menus. Colors for the menu bar and dropdowns (including highlight colors) can be customized independently, and any unset color falls back to the active theme.

See also: ToolbarControl — for a horizontal strip of action buttons

Quick Start

MenuControl menu = Controls.Menu()
    .Horizontal()
    .WithName("mainMenu")
    .Sticky()
    .AddItem("File", m => m
        .AddItem("New", "Ctrl+N", () => NewFile())
        .AddItem("Open", "Ctrl+O", () => OpenFile())
        .AddSeparator()
        .AddItem("Exit", "Alt+F4", () => window.Close()))
    .AddItem("Edit", m => m
        .AddItem("Undo", "Ctrl+Z", () => Undo())
        .AddItem("Redo", "Ctrl+Y", () => Redo()))
    .Build();

menu.StickyPosition = StickyPosition.Top;
window.AddControl(menu);

Builder API

Create a MenuBuilder through the Controls factory:

var builder = Controls.Menu();

Orientation and Behavior

.Horizontal()                               // Menu bar style (default)
.Vertical()                                 // Sidebar style
.Sticky()                                   // Keep focus while a dropdown is open
.WithName(string name)                      // Identify the control

Adding Items

.AddItem(string text, Action<MenuItemBuilder> configure)        // Item with a submenu
.AddItem(string text, Action action)                            // Leaf item
.AddItem(string text, Action action, Color foregroundColor)
.AddItem(string text, string shortcut, Action action)
.AddItem(string text, string shortcut, Action action, Color foregroundColor)
.AddSeparator()                             // Horizontal separator line
.WithMenuBarBackgroundColor(Color color)
.WithMenuBarForegroundColor(Color color)
.WithMenuBarHighlightBackgroundColor(Color color)
.WithMenuBarHighlightForegroundColor(Color color)
.WithMenuBarColors(Color background, Color foreground, Color highlightBackground, Color highlightForeground)
.WithDropdownBackgroundColor(Color color)
.WithDropdownForegroundColor(Color color)
.WithDropdownHighlightBackgroundColor(Color color)
.WithDropdownHighlightForegroundColor(Color color)
.WithDropdownColors(Color background, Color foreground, Color highlightBackground, Color highlightForeground)

Events

.OnItemSelected(EventHandler<MenuItem> handler)
.OnItemSelected(WindowEventHandler<MenuItem> handler)       // Handler also receives the parent window
.OnItemHovered(EventHandler<MenuItem> handler)

Building

MenuControl control = builder.Build();

// Implicit conversion is also supported:
MenuControl control = Controls.Menu().AddItem("File", () => { });

Submenus are configured with a nested MenuItemBuilder (the argument to AddItem(text, configure)):

.AddItem(string text, Action action)                            // Child leaf item
.AddItem(string text, Action action, Color foregroundColor)
.AddItem(string text, string shortcut, Action action)
.AddItem(string text, string shortcut, Action action, Color foregroundColor)
.AddItem(string text, Action<MenuItemBuilder> configure)        // Nested submenu
.AddSeparator()
.Disabled()                                 // Mark this item disabled
.WithShortcut(string shortcut)              // Shortcut hint (display only)
.WithAction(Action action)
.WithTag(object tag)
.WithForegroundColor(Color color)

Properties

PropertyTypeDefaultDescription
OrientationMenuOrientationHorizontalMenu bar (Horizontal) or sidebar (Vertical) layout
IsStickyboolfalseKeep menu focus while a dropdown is open
ItemsMenuItemCollectionemptyLive, observable collection of top-level items
IsEnabledbooltrueEnable/disable all menu interaction
HasFocusboolfalseWhether the menu currently has keyboard focus
CanReceiveFocusbooltrueWhether the menu can receive focus (mirrors IsEnabled)
MenuBarBackgroundColorColor?nullMenu bar background (null = theme default)
MenuBarForegroundColorColor?nullMenu bar foreground (null = theme default)
MenuBarHighlightBackgroundColorColor?nullHighlighted menu bar item background (null = theme)
MenuBarHighlightForegroundColorColor?nullHighlighted menu bar item foreground (null = theme)
DropdownBackgroundColorColor?nullDropdown background (null = theme default)
DropdownForegroundColorColor?nullDropdown item foreground (null = theme default)
DropdownHighlightBackgroundColorColor?nullHighlighted dropdown item background (null = theme)
DropdownHighlightForegroundColorColor?nullHighlighted dropdown item foreground (null = theme)
WantsMouseEventsbooltrueWhether the control receives mouse events (mirrors IsEnabled)
CanFocusWithMousebooltrueWhether mouse clicks can focus the menu (mirrors IsEnabled)

The legacy aliases BackgroundColor, ForegroundColor, HighlightColor, and HighlightForeground are [Obsolete] and map to the corresponding Dropdown* colors. Use the named color properties above instead.

PropertyTypeDefaultDescription
Textstring""Display text (supports markup)
Shortcutstring?nullRight-aligned shortcut hint (display only, not handled)
ForegroundColorColor?nullCustom foreground (ignored when disabled or highlighted)
IsEnabledbooltrueWhether the item can be selected
IsSeparatorboolfalseRender this item as a separator line
Tagobject?nullUser-defined data
ParentMenuItem?nullParent item (null for top-level)
ChildrenMenuItemCollectionemptyObservable child submenu items
HasChildrenboolWhether this item has any children
ActionAction?nullInvoked when selected (not called for items with children)
IsOpenboolfalseWhether this item's dropdown is open (managed internally)

Events

EventArgumentsDescription
ItemSelectedMenuItemFired when a leaf item is selected/executed
ItemSelectedAsyncMenuItemAsync counterpart of ItemSelected
ItemHoveredMenuItemFired when an item is hovered
MouseClickMouseEventArgsFired when a menu item is clicked
MouseRightClickMouseEventArgsFired on right-click (also closes open menus)
MouseEnterMouseEventArgsFired when the mouse enters the control
MouseLeaveMouseEventArgsFired when the mouse leaves the control

Keyboard Support

Horizontal (menu bar)

KeyAction
Left ArrowPrevious top-level item (closes/switches dropdown; in a submenu, returns to parent)
Right ArrowOpen focused item's submenu, or move to next top-level item
Down ArrowOpen the focused item's dropdown; navigate down within an open dropdown
Up ArrowNavigate up within an open dropdown
EnterOpen submenu (if item has children) or execute the focused item
EscapeClose current submenu/dropdown, or unfocus the menu (unless sticky)
HomeJump to first item
EndJump to last item
Letter keyJump to the next item whose text starts with that letter

Vertical (sidebar)

KeyAction
Up ArrowPrevious item (or navigate up within an open dropdown)
Down ArrowNext item (or navigate down within an open dropdown)
Right ArrowOpen the focused item's submenu
Left ArrowClose the current submenu and return to parent
EnterOpen submenu (if item has children) or execute the focused item
EscapeClose current submenu/dropdown, or unfocus the menu (unless sticky)
HomeJump to first item
EndJump to last item
Letter keyJump to the next item whose text starts with that letter

Mouse Support

ActionResult
Click on top-level itemFocus the menu and open its dropdown (or execute if it has no children)
Click on item with childrenOpen its submenu
Click on leaf itemExecute the item's action and close all menus
Hover over item with childrenOpen the submenu after a short aim delay
Hover over top-level item (dropdown open)Switch to that item's dropdown
Mouse wheel over dropdownScroll the dropdown when it exceeds the visible height
Right-clickClose all open menus and fire MouseRightClick
Click outside / window deactivatedDismiss all open menus

Examples

Horizontal Menu Bar (IDE-style)

MenuControl menu = Controls.Menu()
    .Horizontal()
    .WithName("mainMenu")
    .Sticky()
    .AddItem("File", m => m
        .AddItem("New", "Ctrl+N", HandleNewFile)
        .AddItem("Open", "Ctrl+O", () => FileDialogs.ShowFilePickerAsync(ws))
        .AddSeparator()
        .AddItem("Save", "Ctrl+S", HandleSaveFile)
        .AddItem("Save As...", "Ctrl+Shift+S", () => FileDialogs.ShowSaveFileAsync(ws))
        .AddSeparator()
        .AddItem("Exit", "Alt+F4", () => ws.CloseWindow(window)))
    .AddItem("Edit", m => m
        .AddItem("Undo", "Ctrl+Z", () => Undo())
        .AddItem("Redo", "Ctrl+Y", () => Redo())
        .AddSeparator()
        .AddItem("Cut", "Ctrl+X", () => Cut())
        .AddItem("Copy", "Ctrl+C", () => Copy())
        .AddItem("Paste", "Ctrl+V", () => Paste()))
    .AddItem("Help", m => m
        .AddItem("Documentation", "F1", () => ShowDocs())
        .AddItem("About", () => AboutDialog.Show(ws)))
    .Build();

menu.StickyPosition = StickyPosition.Top;
window.AddControl(menu);

Vertical Sidebar Menu

MenuControl sidebar = Controls.Menu()
    .Vertical()
    .AddItem("Dashboard", () => ShowDashboard())
    .AddItem("Reports", m => m
        .AddItem("Daily", () => ShowDaily())
        .AddItem("Weekly", () => ShowWeekly())
        .AddItem("Monthly", () => ShowMonthly()))
    .AddItem("Settings", () => ShowSettings())
    .Build();

window.AddControl(sidebar);

Nested Submenus

MenuControl menu = Controls.Menu()
    .AddItem("Insert", m => m
        .AddItem("Image", () => InsertImage())
        .AddItem("Table", t => t
            .AddItem("2x2", () => InsertTable(2, 2))
            .AddItem("3x3", () => InsertTable(3, 3))
            .AddItem("Custom...", () => InsertCustomTable()))
        .AddSeparator()
        .AddItem("Page Break", () => InsertPageBreak()))
    .Build();

Disabled Items, Colors, and Tags via MenuItemBuilder

MenuControl menu = Controls.Menu()
    .AddItem("Account", m => m
        .AddItem("Profile", () => ShowProfile())
        .AddSeparator()
        .AddItem("Delete Account", b => b
            .WithAction(() => DeleteAccount())
            .WithForegroundColor(Color.Red)
            .WithTag("danger"))
        .AddItem("Premium (Locked)", b => b
            .WithAction(() => { })
            .Disabled()))
    .Build();

Customizing Colors

MenuControl menu = Controls.Menu()
    .Horizontal()
    .WithMenuBarColors(
        background: Color.Grey15,
        foreground: Color.White,
        highlightBackground: Color.Blue,
        highlightForeground: Color.White)
    .WithDropdownColors(
        background: Color.Grey11,
        foreground: Color.Silver,
        highlightBackground: Color.Blue,
        highlightForeground: Color.White)
    .AddItem("File", m => m.AddItem("New", () => NewFile()))
    .Build();

Handling Selection with Window Access

MenuControl menu = Controls.Menu()
    .AddItem("File", m => m
        .AddItem("Close", () => { }))
    .OnItemSelected((sender, item, window) =>
    {
        windowSystem.NotificationStateService.ShowNotification(
            "Menu",
            $"Selected: {item.GetPath()}",
            NotificationSeverity.Info);
    })
    .Build();

Runtime Manipulation by Path

// Find and update items after the menu is built
var saveItem = menu.FindItemByPath("File/Save");

// Enable/disable by path
menu.SetItemEnabled("File/Save", isDirty);

// Open a dropdown programmatically
menu.OpenDropdown("File");

// Add and remove items via the live Items collection
menu.AddItem(new MenuItem { Text = "Recent", Action = () => ShowRecent() });
menu.ClearItems();

Best Practices

  1. Use .Horizontal() for menu bars and .Vertical() for sidebars — set StickyPosition.Top on a horizontal bar so it stays pinned.
  2. Provide shortcut hints with the shortcut overloads ("Ctrl+S"); these are display-only — wire the real keybindings via the window's key handling.
  3. Group related items with .AddSeparator() to keep long dropdowns scannable.
  4. Disable rather than hide unavailable actions using MenuItem.IsEnabled or .Disabled(), and update them via SetItemEnabled(path, enabled).
  5. Use .Sticky() when you want the menu to retain focus while dropdowns are open (common for keyboard-driven menu bars).
  6. Mutate menus from the UI thread — modify Items/Children or call OpenDropdown/CloseAllMenus on the UI thread (use windowSystem.EnqueueOnUIThread from background work).

See Also


Back to Controls | Back to Main Documentation