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 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 ToolStripSeparator is a normal item, so inserting one dynamically means inserting at the right index.
  • ContextMenuStrip has the same item model; assign it to a control with control.ContextMenuStrip = menu so right-click positions it for you.
  • Use the Opening event 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);
ItemUse it forGotcha
ToolStripButtonA single commandSet ToolTipText, not Text, for icon-only buttons
ToolStripDropDownButtonA command with alternativesClicking the body never fires unless you handle Click too
ToolStripSplitButtonRepeat last action plus alternativesDropDownItems excludes the button action itself
ToolStripSeparatorGroupingIt is an item; account for it when inserting by index
ToolStripComboBoxList values inlineSet AutoSize = false and give it an explicit width
ToolStripTextBoxSearch or filter inputKeep 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 = true makes 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.

Dialogs, multiple forms and MDI Events and layout

Last refreshed 2026-09-18.