Menus Deep Dive
Terminal.Gui provides a comprehensive, hierarchical menu system built on top of the @Terminal.Gui.Shortcut and @Terminal.Gui.Bar classes. This deep dive covers the architecture, class relationships, and interactions between the menu components.
Table of Contents
- Overview
- Class Hierarchy
- Component Descriptions
- Architecture
- Interactions
- Usage Examples
- Keyboard Navigation
- Event Flow
Overview
The menu system in Terminal.Gui consists of the following key components:
| Component | Description |
|---|---|
| @Terminal.Gui.Shortcut | Base class for displaying a command, help text, and key binding |
| @Terminal.Gui.Bar | Container for Shortcut items, supports horizontal/vertical orientation |
| @Terminal.Gui.MenuItem | A Shortcut-derived item for use in menus, supports submenus |
| @Terminal.Gui.Menu | A vertically-oriented Bar that contains MenuItem items |
| @Terminal.Gui.MenuBarItem | A MenuItem that holds a PopoverMenu instead of a SubMenu |
| @Terminal.Gui.MenuBar | A horizontal Menu that contains MenuBarItem items |
| @Terminal.Gui.PopoverMenu | A PopoverBaseImpl-derived view that hosts cascading menus |
Class Hierarchy
The menu system builds upon a layered class hierarchy:
View
├── Shortcut // Command + HelpText + Key display
│ └── MenuItem // Menu-specific Shortcut with SubMenu support
│ └── MenuBarItem // MenuItem that uses PopoverMenu instead of SubMenu
│
├── Bar // Container for Shortcuts (horizontal/vertical)
│ └── Menu // Vertical Bar for MenuItems
│ └── MenuBar // Horizontal Menu for MenuBarItems
│
└── PopoverBaseImpl // Base for popover views
└── PopoverMenu // Cascading menu popover
Inheritance Details
Shortcut → MenuItem → MenuBarItem:
Shortcutdisplays command text, help text, and a key bindingMenuItemextendsShortcutto addSubMenusupport for nested menusMenuBarItemextendsMenuItembut replacesSubMenuwithPopoverMenu
Bar → Menu → MenuBar:
Baris a generic container forShortcutviews with orientation supportMenuis a verticalBarspecialized forMenuItemitemsMenuBaris a horizontalMenuspecialized forMenuBarItemitems
For completeness, here's how StatusBar fits in:
Bar → StatusBar:
StatusBaris a horizontalBarspecialized forShortcutsitems
Component Descriptions
Shortcut
@Terminal.Gui.Shortcut is the foundational building block. It displays three elements:
- CommandView - The command text (left side by default)
- HelpView - Help text (middle)
- KeyView - Key binding display (right side)
Shortcut shortcut = new ()
{
Title = "_Save", // CommandView text with hotkey
HelpText = "Save the file",
Key = Key.S.WithCtrl,
Action = () => SaveFile ()
};
IMPORTANT: The CommandView, HelpView, and KeyView are subviews of the shortcut. But how they are managed is an implementation detail and shortcut.SubViews should not be used to try to access them.
Key features:
- Supports
Actionfor direct invocation BindKeyToApplicationenables application-wide key bindingsAlignmentModescontrols element ordering (start-to-end or end-to-start)CommandViewcan be replaced with custom views (e.g.,CheckBox)
Bar
@Terminal.Gui.Bar is a container that arranges Shortcut items either horizontally or vertically:
Bar statusBar = new ()
{
Orientation = Orientation.Horizontal,
Y = Pos.AnchorEnd ()
};
statusBar.Add (new Shortcut { Title = "_Help", Key = Key.F1 });
statusBar.Add (new Shortcut { Title = "_Quit", Key = Key.Q.WithCtrl });
Key features:
Orientationproperty controls layout directionAlignmentModesproperty controls item alignment- Supports mouse wheel navigation
- Auto-sizes based on content (
Dim.Auto)
MenuItem
@Terminal.Gui.MenuItem extends Shortcut for use in menus:
MenuItem menuItem = new ()
{
Title = "_Open...",
HelpText = "Open a file",
Key = Key.O.WithCtrl,
Action = () => OpenFile ()
};
// Or bind to a command on a target view
MenuItem boundItem = new (myView, Command.Save);
Key features:
SubMenuproperty holds nested @Terminal.Gui.MenuTargetViewandCommandenable command binding- Automatically gets focus on mouse enter
- Displays right-arrow glyph when it has a submenu
Menu
@Terminal.Gui.Menu is a vertical Bar specialized for menu items:
Menu fileMenu = new ([
new MenuItem ("_New", Key.N.WithCtrl, () => NewFile ()),
new MenuItem ("_Open...", Key.O.WithCtrl, () => OpenFile ()),
new Line (), // Separator
new MenuItem ("_Save", Key.S.WithCtrl, () => SaveFile ()),
new MenuItem ("Save _As...", () => SaveAs ())
]);
Key features:
- Vertical orientation by default
SuperMenuItemproperty links back to parentMenuItemSelectedMenuItemtracks current selection- Supports
Lineseparators between items - Uses
Schemes.Menucolor scheme by default
MenuBarItem
@Terminal.Gui.MenuBarItem extends MenuItem for use in @Terminal.Gui.MenuBar:
MenuBarItem fileMenuBarItem = new ("_File", [
new MenuItem ("_New", Key.N.WithCtrl, () => NewFile ()),
new MenuItem ("_Open...", Key.O.WithCtrl, () => OpenFile ()),
new Line (),
new MenuItem ("_Quit", Application.QuitKey, () => Application.RequestStop ())
]);
Important: MenuBarItem uses PopoverMenu instead of SubMenu. Attempting to set SubMenu will throw InvalidOperationException.
Key features:
PopoverMenuproperty holds the dropdown menuPopoverMenuOpentracks whether the popover is visiblePopoverMenuOpenChangedevent fires when visibility changes
MenuBar
@Terminal.Gui.MenuBar is a horizontal menu bar typically placed at the top of a window:
MenuBar menuBar = new ([
new MenuBarItem ("_File", [
new MenuItem ("_New", Key.N.WithCtrl, () => NewFile ()),
new MenuItem ("_Open...", Key.O.WithCtrl, () => OpenFile ()),
new Line (),
new MenuItem ("E_xit", Application.QuitKey, () => Application.RequestStop ())
]),
new MenuBarItem ("_Edit", [
new MenuItem ("_Cut", Key.X.WithCtrl, () => Cut ()),
new MenuItem ("_Copy", Key.C.WithCtrl, () => Copy ()),
new MenuItem ("_Paste", Key.V.WithCtrl, () => Paste ())
]),
new MenuBarItem ("_Help", [
new MenuItem ("_About...", () => ShowAbout ())
])
]);
// Add to window
window.Add (menuBar);
Key features:
Keyproperty defines the activation key (default:F9)Activeproperty indicates whether the menu bar is activeIsOpen()returns whether any popover menu is visibleDefaultBorderStyleconfigurable via themes- Automatically positions at top with
Width = Dim.Fill ()
PopoverMenu
@Terminal.Gui.PopoverMenu is a popover that hosts cascading menus:
// Create a context menu
PopoverMenu contextMenu = new ([
new MenuItem (targetView, Command.Cut),
new MenuItem (targetView, Command.Copy),
new MenuItem (targetView, Command.Paste),
new Line (),
new MenuItem (targetView, Command.SelectAll)
]);
// Register with application (required!)
Application.Popover?.Register (contextMenu);
// Show at mouse position
contextMenu.MakeVisible ();
// Or show at specific position
contextMenu.MakeVisible (new Point (10, 5));
Key features:
Rootproperty holds the top-level @Terminal.Gui.MenuKeyproperty for activation (default:Shift+F10)MouseFlagsproperty defines mouse button to show menu (default: right-click)- Auto-positions to ensure visibility on screen
- Cascading submenus shown automatically on selection
Important: See the Popovers Deep Dive for complete details on popover lifecycle and requirements.
Architecture
Relationship Diagram
┌─────────────────────────────────────────────────────────────────────┐
│ Window │
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ MenuBar │ │
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │
│ │ │ MenuBarItem │ │ MenuBarItem │ │ MenuBarItem │ ... │ │
│ │ │ "File" │ │ "Edit" │ │ "Help" │ │ │
│ │ └──────┬──────┘ └─────────────┘ └─────────────┘ │ │
│ └─────────│─────────────────────────────────────────────────────┘ │
│ │ │
│ │ owns │
│ ▼ │
│ ┌──────────────────┐ │
│ │ PopoverMenu │ ◄─── Registered with Application.Popover │
│ │ ┌────────────┐ │ │
│ │ │ Menu │ │ ◄─── Root Menu │
│ │ │ (Root) │ │ │
│ │ │ ┌────────┐ │ │ │
│ │ │ │MenuItem│─┼──┼──► SubMenu ──► Menu ──► MenuItem ──► SubMenu │
│ │ │ │MenuItem│ │ │ ▲ │
│ │ │ │ Line │ │ │ Cascading │ │
│ │ │ │MenuItem│ │ │ Hierarchy ─────┘ │
│ │ │ └────────┘ │ │ │
│ │ └────────────┘ │ │
│ └──────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
Key Relationships
MenuBar contains MenuBarItems:
MenuBaris a horizontalMenucontainingMenuBarItemsubviews- Each
MenuBarItemowns aPopoverMenu
MenuBarItem owns PopoverMenu:
MenuBarItem.PopoverMenuproperty holds the dropdown- Events are wired up automatically for visibility and acceptance
PopoverMenu contains Root Menu:
PopoverMenu.Rootis the top-levelMenu- The
PopoverMenumanages showing/hiding of cascading menus
Menu contains MenuItems:
Menu.SubViewscontainsMenuIteminstancesMenu.SelectedMenuItemtracks the focused item
MenuItem may contain SubMenu:
MenuItem.SubMenuholds a nestedMenufor cascadingMenu.SuperMenuItemlinks back to the parentMenuItem
Interactions
MenuBar Activation Flow
- User presses
F9(default) or clicks onMenuBar MenuBar.Activeis set totrueMenuBar.CanFocusbecomestrue- First
MenuBarItemwith aPopoverMenuis selected PopoverMenu.MakeVisible()is called
PopoverMenu Display Flow
MakeVisible()is called (optionally with position)SetPosition()calculates visible locationApplication.Popover.Show()is invokedOnVisibleChanged()adds and shows theRootmenu- First
MenuItemreceives focus
Menu Selection Flow
- User navigates with arrow keys or mouse
Menu.Focusedchanges to newMenuItemMenu.SelectedMenuItemChangedevent fires- If new item has
SubMenu,PopoverMenu.ShowSubMenu()is called - Previous peer submenus are hidden
MenuItem Acceptance Flow
- User presses Enter or clicks on
MenuItem MenuItem.DispatchCommand()is called- If
TargetViewexists, command is invoked on target - Otherwise,
Actionis invoked AcceptingandAcceptedevents propagate upPopoverMenuhides (unless item has submenu)
Keyboard Navigation
| Key | Action |
|---|---|
F9 |
Toggle MenuBar activation |
Shift+F10 |
Show context PopoverMenu |
↑ / ↓ |
Navigate within Menu |
← / → |
Navigate MenuBar items / Expand-collapse submenus |
Enter |
Accept selected MenuItem |
Escape |
Close menu / Deactivate MenuBar |
| Hotkey | Jump to MenuItem with matching hotkey |
Usage Examples
Basic MenuBar
using Terminal.Gui;
Application.Init ();
Window mainWindow = new () { Title = "Menu Demo" };
MenuBar menuBar = new ([
new MenuBarItem ("_File", [
new MenuItem ("_New", "", () => MessageBox.Query ("New", "Create new file?", "OK", "Cancel")),
new MenuItem ("_Open...", "", () => MessageBox.Query ("Open", "Open file dialog", "OK")),
new Line (),
new MenuItem ("E_xit", Application.QuitKey, () => Application.RequestStop ())
]),
new MenuBarItem ("_Edit", [
new MenuItem ("_Undo", Key.Z.WithCtrl, () => { }),
new Line (),
new MenuItem ("Cu_t", Key.X.WithCtrl, () => { }),
new MenuItem ("_Copy", Key.C.WithCtrl, () => { }),
new MenuItem ("_Paste", Key.V.WithCtrl, () => { })
])
]);
mainWindow.Add (menuBar);
Application.Run (mainWindow);
Application.Shutdown ();
Nested Submenus
MenuBarItem optionsMenu = new ("_Options", [
new MenuItem
{
Title = "_Preferences",
SubMenu = new Menu ([
new MenuItem { Title = "_General", Action = () => ShowGeneralPrefs () },
new MenuItem { Title = "_Editor", Action = () => ShowEditorPrefs () },
new MenuItem
{
Title = "_Advanced",
SubMenu = new Menu ([
new MenuItem { Title = "_Debug Mode", Action = () => ToggleDebug () },
new MenuItem { Title = "_Experimental", Action = () => ToggleExperimental () }
])
}
])
},
new Line (),
new MenuItem { Title = "_Reset to Defaults", Action = () => ResetDefaults () }
]);
Command Binding
// Bind menu items to commands on a target view
TextView editor = new () { X = 0, Y = 1, Width = Dim.Fill (), Height = Dim.Fill () };
MenuBar menuBar = new ([
new MenuBarItem ("_Edit", [
new MenuItem (editor, Command.Cut), // Uses editor's Cut command
new MenuItem (editor, Command.Copy), // Uses editor's Copy command
new MenuItem (editor, Command.Paste), // Uses editor's Paste command
new Line (),
new MenuItem (editor, Command.SelectAll)
])
]);
CheckBox in Menu
CheckBox wordWrapCheckBox = new () { Title = "_Word Wrap" };
wordWrapCheckBox.CheckedStateChanging += (s, e) =>
{
editor.WordWrap = e.NewValue == CheckState.Checked;
};
MenuBarItem viewMenu = new ("_View", [
new MenuItem { CommandView = wordWrapCheckBox },
new MenuItem
{
CommandView = new CheckBox { Title = "_Line Numbers" },
Key = Key.L.WithCtrl
}
]);
Context Menu (PopoverMenu)
PopoverMenu contextMenu = new ([
new MenuItem (editor, Command.Cut),
new MenuItem (editor, Command.Copy),
new MenuItem (editor, Command.Paste),
new Line (),
new MenuItem { Title = "_Properties...", Action = () => ShowProperties () }
]);
Application.Popover?.Register (contextMenu);
// Show on right-click
editor.MouseClick += (s, e) =>
{
if (e.Flags.HasFlag (MouseFlags.RightButtonClicked))
{
contextMenu.MakeVisible (e.ScreenPosition);
e.Handled = true;
}
};
Event Flow
Acceptance Event Propagation
When a MenuItem is accepted, events propagate through the hierarchy:
MenuItem.Accepting → MenuItem.Accepted
↓ ↓
Menu.Accepting → Menu.Accepted
↓ ↓
PopoverMenu.Accepting → PopoverMenu.Accepted
↓ ↓
MenuBarItem.Accepting → MenuBarItem.Accepted
↓ ↓
MenuBar.Accepting → MenuBar.Accepted
Selection Change Events
User navigates → Menu.Focused changes
↓
Menu.OnFocusedChanged ()
↓
SelectedMenuItem updated
↓
SelectedMenuItemChanged event
↓
PopoverMenu shows/hides submenus
Key Binding Resolution
- Check
KeyBindingson focusedMenuItem - Check
HotKeyBindingsonMenu - Check
KeyBindingsonPopoverMenu - Check
KeyBindingsonMenuBar - Check
Application.Keyboard.KeyBindings
Configuration
Menu appearance can be customized via themes:
// Set default border style for menus
Menu.DefaultBorderStyle = LineStyle.Single;
// Set default border style for menu bars
MenuBar.DefaultBorderStyle = LineStyle.None;
// Set default activation key for menu bars
MenuBar.DefaultKey = Key.F10;
// Set default activation key for popover menus
PopoverMenu.DefaultKey = Key.F10.WithShift;
These can also be configured in config.json:
{
"Themes": {
"Default": {
"Menu.DefaultBorderStyle": "Single",
"MenuBar.DefaultBorderStyle": "None"
}
},
"Settings": {
"MenuBar.DefaultKey": "F9",
"PopoverMenu.DefaultKey": "Shift+F10"
}
}
See Also
- Popovers Deep Dive - Complete details on popover lifecycle
- Command Deep Dive - Command binding and dispatch
- Keyboard Deep Dive - Key binding system
- Events Deep Dive - Event handling patterns
- @Terminal.Gui.MenuBar API Reference
- @Terminal.Gui.PopoverMenu API Reference
- @Terminal.Gui.MenuItem API Reference