MenuControl
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
Menu Bar Colors
.WithMenuBarBackgroundColor(Color color)
.WithMenuBarForegroundColor(Color color)
.WithMenuBarHighlightBackgroundColor(Color color)
.WithMenuBarHighlightForegroundColor(Color color)
.WithMenuBarColors(Color background, Color foreground, Color highlightBackground, Color highlightForeground)
Dropdown Colors
.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", () => { });
MenuItemBuilder
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
| Property | Type | Default | Description |
|---|---|---|---|
Orientation |
MenuOrientation |
Horizontal |
Menu bar (Horizontal) or sidebar (Vertical) layout |
IsSticky |
bool |
false |
Keep menu focus while a dropdown is open |
Items |
MenuItemCollection |
empty | Live, observable collection of top-level items |
IsEnabled |
bool |
true |
Enable/disable all menu interaction |
HasFocus |
bool |
false |
Whether the menu currently has keyboard focus |
CanReceiveFocus |
bool |
true |
Whether the menu can receive focus (mirrors IsEnabled) |
MenuBarBackgroundColor |
Color? |
null |
Menu bar background (null = theme default) |
MenuBarForegroundColor |
Color? |
null |
Menu bar foreground (null = theme default) |
MenuBarHighlightBackgroundColor |
Color? |
null |
Highlighted menu bar item background (null = theme) |
MenuBarHighlightForegroundColor |
Color? |
null |
Highlighted menu bar item foreground (null = theme) |
DropdownBackgroundColor |
Color? |
null |
Dropdown background (null = theme default) |
DropdownForegroundColor |
Color? |
null |
Dropdown item foreground (null = theme default) |
DropdownHighlightBackgroundColor |
Color? |
null |
Highlighted dropdown item background (null = theme) |
DropdownHighlightForegroundColor |
Color? |
null |
Highlighted dropdown item foreground (null = theme) |
WantsMouseEvents |
bool |
true |
Whether the control receives mouse events (mirrors IsEnabled) |
CanFocusWithMouse |
bool |
true |
Whether mouse clicks can focus the menu (mirrors IsEnabled) |
The legacy aliases
BackgroundColor,ForegroundColor,HighlightColor, andHighlightForegroundare[Obsolete]and map to the correspondingDropdown*colors. Use the named color properties above instead.
MenuItem Properties
| Property | Type | Default | Description |
|---|---|---|---|
Text |
string |
"" |
Display text (supports markup) |
Shortcut |
string? |
null |
Right-aligned shortcut hint (display only, not handled) |
ForegroundColor |
Color? |
null |
Custom foreground (ignored when disabled or highlighted) |
IsEnabled |
bool |
true |
Whether the item can be selected |
IsSeparator |
bool |
false |
Render this item as a separator line |
Tag |
object? |
null |
User-defined data |
Parent |
MenuItem? |
null |
Parent item (null for top-level) |
Children |
MenuItemCollection |
empty | Observable child submenu items |
HasChildren |
bool |
— | Whether this item has any children |
Action |
Action? |
null |
Invoked when selected (not called for items with children) |
IsOpen |
bool |
false |
Whether this item's dropdown is open (managed internally) |
Events
| Event | Arguments | Description |
|---|---|---|
ItemSelected |
MenuItem |
Fired when a leaf item is selected/executed |
ItemSelectedAsync |
MenuItem |
Async counterpart of ItemSelected |
ItemHovered |
MenuItem |
Fired when an item is hovered |
MouseClick |
MouseEventArgs |
Fired when a menu item is clicked |
MouseRightClick |
MouseEventArgs |
Fired on right-click (also closes open menus) |
MouseEnter |
MouseEventArgs |
Fired when the mouse enters the control |
MouseLeave |
MouseEventArgs |
Fired when the mouse leaves the control |
Keyboard Support
Horizontal (menu bar)
| Key | Action |
|---|---|
| Left Arrow | Previous top-level item (closes/switches dropdown; in a submenu, returns to parent) |
| Right Arrow | Open focused item's submenu, or move to next top-level item |
| Down Arrow | Open the focused item's dropdown; navigate down within an open dropdown |
| Up Arrow | Navigate up within an open dropdown |
| Enter | Open submenu (if item has children) or execute the focused item |
| Escape | Close current submenu/dropdown, or unfocus the menu (unless sticky) |
| Home | Jump to first item |
| End | Jump to last item |
| Letter key | Jump to the next item whose text starts with that letter |
Vertical (sidebar)
| Key | Action |
|---|---|
| Up Arrow | Previous item (or navigate up within an open dropdown) |
| Down Arrow | Next item (or navigate down within an open dropdown) |
| Right Arrow | Open the focused item's submenu |
| Left Arrow | Close the current submenu and return to parent |
| Enter | Open submenu (if item has children) or execute the focused item |
| Escape | Close current submenu/dropdown, or unfocus the menu (unless sticky) |
| Home | Jump to first item |
| End | Jump to last item |
| Letter key | Jump to the next item whose text starts with that letter |
Mouse Support
| Action | Result |
|---|---|
| Click on top-level item | Focus the menu and open its dropdown (or execute if it has no children) |
| Click on item with children | Open its submenu |
| Click on leaf item | Execute the item's action and close all menus |
| Hover over item with children | Open the submenu after a short aim delay |
| Hover over top-level item (dropdown open) | Switch to that item's dropdown |
| Mouse wheel over dropdown | Scroll the dropdown when it exceeds the visible height |
| Right-click | Close all open menus and fire MouseRightClick |
| Click outside / window deactivated | Dismiss 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
- Use
.Horizontal()for menu bars and.Vertical()for sidebars — setStickyPosition.Topon a horizontal bar so it stays pinned. - Provide shortcut hints with the
shortcutoverloads ("Ctrl+S"); these are display-only — wire the real keybindings via the window's key handling. - Group related items with
.AddSeparator()to keep long dropdowns scannable. - Disable rather than hide unavailable actions using
MenuItem.IsEnabledor.Disabled(), and update them viaSetItemEnabled(path, enabled). - Use
.Sticky()when you want the menu to retain focus while dropdowns are open (common for keyboard-driven menu bars). - Mutate menus from the UI thread — modify
Items/Childrenor callOpenDropdown/CloseAllMenuson the UI thread (usewindowSystem.EnqueueOnUIThreadfrom background work).
See Also
- ToolbarControl - Horizontal strip of action buttons
- DropdownControl - Single-selection dropdown / combo box
- ListControl - Scrollable selectable list