Menus, toolbars and status bars
Build the standard desktop chrome: a MenuStrip with shortcuts, context menus, a ToolStrip with dropdowns, and a StatusStrip that reports state without stealing focus.
MenuStrip and ContextMenuStrip
MenuStrip is a container of ToolStripMenuItem objects. You can build it in the designer, but the code below is what the designer produces - knowing it makes dynamic menus possible.
var fileMenu = new ToolStripMenuItem("&File");
var recent = new ToolStripMenuItem("Open &recent");
recent.DropDownItems.Add(new ToolStripMenuItem("report.csv") { Tag = @"C:\data\report.csv" });
recent.DropDownOpening += (s, e) =>
{
// rebuild state only when the menu opens, not on every file-system change
var parent = (ToolStripMenuItem)s;
foreach (ToolStripMenuItem item in parent.DropDownItems)
item.ForeColor = File.Exists((string)item.Tag) ? SystemColors.ControlText : SystemColors.GrayText;
};
var openItem = new ToolStripMenuItem("&Open...", null, OnOpenClicked);
openItem.ShortcutKeys = Keys.Control | Keys.O;
fileMenu.DropDownItems.AddRange(new ToolStripItem[] { openItem, recent, new ToolStripSeparator() });
fileMenu.DropDownItems.Add(new ToolStripMenuItem("E&xit", null, (s, e) => Close()));
MainMenuStrip = new MenuStrip();
MainMenuStrip.Items.Add(fileMenu);
Controls.Add(MainMenuStrip);- Put a literal ampersand in a caption by doubling it:
"Save && Close". - A
ToolStripSeparatoris a normal item, so inserting one dynamically means inserting at the right index. ContextMenuStriphas the same item model; assign it to a control withcontrol.ContextMenuStrip = menuso right-click positions it for you.- Use the
Openingevent to enable or disable commands rather than doing it after every selection change.
ToolStrip with buttons and dropdowns
var toolbar = new ToolStrip
{
GripStyle = ToolStripGripStyle.Hidden,
ImageScalingSize = new Size(16, 16),
RenderMode = ToolStripRenderMode.System
};
toolbar.Items.Add(new ToolStripButton("Save", saveIcon, OnSaveClicked) { DisplayStyle = ToolStripItemDisplayStyle.ImageAndText });
var exportDrop = new ToolStripDropDownButton("Export");
exportDrop.DropDownItems.Add("As CSV", null, (s, e) => Export("csv"));
exportDrop.DropDownItems.Add("As JSON", null, (s, e) => Export("json"));
toolbar.Items.Add(exportDrop);
// a split button does the last action on click and shows alternatives on the arrow
var refresh = new ToolStripSplitButton("Refresh", null, OnRefreshClicked);
refresh.DropDownItems.Add("Refresh all", null, (s, e) => RefreshAll());
toolbar.Items.Add(refresh);
Controls.Add(toolbar);| Item | Use it for | Gotcha |
|---|---|---|
| ToolStripButton | A single command | Set ToolTipText, not Text, for icon-only buttons |
| ToolStripDropDownButton | A command with alternatives | Clicking the body never fires unless you handle Click too |
| ToolStripSplitButton | Repeat last action plus alternatives | DropDownItems excludes the button action itself |
| ToolStripSeparator | Grouping | It is an item; account for it when inserting by index |
| ToolStripComboBox | List values inline | Set AutoSize = false and give it an explicit width |
| ToolStripTextBox | Search or filter input | Keep the filter debounced - every keystroke arrives on the UI thread |
Items that do not fit collapse into an overflow chevron automatically. If you want a toolbar pinned below the menu and another above the status bar, drop them into a ToolStripContainer and set the panels you actually want to show.
StatusStrip
var status = new StatusStrip();
var message = new ToolStripStatusLabel("Ready") { Spring = true, TextAlign = ContentAlignment.MiddleLeft };
var count = new ToolStripStatusLabel("0 rows");
var bar = new ToolStripProgressBar { Visible = false, Style = ProgressBarStyle.Marquee };
status.Items.AddRange(new ToolStripItem[] { message, count, bar });
Controls.Add(status);
void SetBusy(bool busy)
{
bar.Visible = busy;
count.Text = busy ? "Loading..." : "0 rows";
}Spring = truemakes that label absorb all leftover width, which is how you keep the count pinned to the right edge.- Never put a status update in a tight loop without throttling - updating a label 10,000 times repaints the window 10,000 times.
- A status bar is a progress display, not a log. Anything the user may need later belongs in a list or a file.
⚠️
ShortcutKeys on a menu item only fires while the form has focus. For a genuinely global hotkey, keep the menu shortcut for discoverability and register a system-wide one with RegisterHotKey via P/Invoke - and unregister it when the form closes.FAQ
Designer or code for menus?
Start in the designer for a fixed menu, then move to code the moment the item list depends on data. Mixed approaches are normal: a designer shell with dynamically filled drop-downs.
Why is my context menu losing its theme?
A custom
RenderMode on the owning ToolStrip applies to that strip only. Use ToolStripManager.Renderer if you want the whole application to share one renderer.Related
Dialogs, multiple forms and MDI Events and layout
Last refreshed 2026-09-18.