Build a classic desktop window frame with a Menu, right click ContextMenu, ToolBar and StatusBar, add access keys and real keyboard shortcuts, and update the status bar from code.

Bishal Biswas
Self Learner
The frame around your app
Open almost any desktop program and you will see the same three bars:
- A menu bar at the top with words like File, Edit and Help.
- A toolbar under it with small buttons for the most used actions.
- A status bar at the bottom that shows short facts, such as “12 expenses” or “Saved”.
People already know how these work, so adding them makes your app feel familiar from the first click. In this lesson you will build all three for CoinJar, the small expense tracker that this course grows step by step.
The usual layout tool for this is a DockPanel (from StackPanel, WrapPanel and DockPanel). The menu and toolbar dock to the top, the status bar docks to the bottom, and the main content fills the rest:
<DockPanel>
<Menu DockPanel.Dock="Top"> ... </Menu>
<ToolBarTray DockPanel.Dock="Top"> ... </ToolBarTray>
<StatusBar DockPanel.Dock="Bottom"> ... </StatusBar>
<!-- The last child fills the space that is left -->
<ListBox />
</DockPanel>
Order matters in a DockPanel. Put the docked bars first and the main content last.
The Menu control
A Menu holds MenuItem elements. Each MenuItem has a Header (the text you see) and can hold more MenuItem elements, which become its drop down list.
<Menu DockPanel.Dock="Top">
<MenuItem Header="_File">
<MenuItem Header="_New expense" Click="NewExpense_Click" />
<MenuItem Header="_Open..." />
<Separator />
<MenuItem Header="E_xit" Click="Exit_Click" />
</MenuItem>
<MenuItem Header="_Help">
<MenuItem Header="_About CoinJar" Click="About_Click" />
</MenuItem>
</Menu>
A few details in there:
Separatordraws a thin line to group related items.Clickworks just like a button click, which you met in Code-Behind and Event Handlers.- The three dots in
Open...are a Windows habit. They tell the user “this opens a window and asks you something first”.
private void Exit_Click(object sender, RoutedEventArgs e)
{
Close(); // closing the main window ends the app
}
private void About_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show("CoinJar 1.0\nA tiny expense tracker.", "About");
}
Access keys with the underscore
The underscore in _File makes F the access key. Press Alt, and the letters get underlined. Then press F to open the File menu, and N to pick “New expense”. Keyboard users love this and it costs you one character.
If you need a real underscore in the text, write two: Header="Save__as" shows “Save_as”.
Sub menus, check marks and icons
Menus can nest as deep as you like, but two levels is plenty for most apps.
<MenuItem Header="_View">
<MenuItem Header="Show _toolbar" IsCheckable="True" IsChecked="True"
Click="ShowToolbar_Click" />
<MenuItem Header="_Sort by">
<MenuItem Header="_Date" />
<MenuItem Header="_Amount" />
</MenuItem>
</MenuItem>
IsCheckable="True" turns the item into an on and off switch with a tick mark. Read IsChecked in your handler to see the new state:
private void ShowToolbar_Click(object sender, RoutedEventArgs e)
{
var item = (MenuItem)sender;
MainToolBarTray.Visibility = item.IsChecked
? Visibility.Visible
: Visibility.Collapsed;
}
The Icon property puts a small picture to the left of the text. Any element works, so the icon font trick from Images, Icons and Media fits nicely:
<MenuItem Header="_Delete" Click="Delete_Click">
<MenuItem.Icon>
<TextBlock FontFamily="Segoe Fluent Icons, Segoe MDL2 Assets" Text="" />
</MenuItem.Icon>
</MenuItem>
Real keyboard shortcuts
You may see InputGestureText="Ctrl+N" on a MenuItem. Be careful: it only shows the text “Ctrl+N” next to the item. It does not make the keys do anything.
To get a working shortcut, use one of WPF’s built in commands. A command is an action with a name, a default shortcut and a default label, which several controls can share. ApplicationCommands.New already knows that its shortcut is Ctrl+N.
You connect a command to your code with a CommandBinding on the window:
<Window.CommandBindings>
<CommandBinding Command="ApplicationCommands.New" Executed="New_Executed" />
</Window.CommandBindings>
private void New_Executed(object sender, ExecutedRoutedEventArgs e)
{
AddExpense("New expense");
}
Now point the menu item at the command instead of a Click handler:
<MenuItem Command="ApplicationCommands.New" Header="_New expense" />
Three things happen for free. The menu shows “Ctrl+N” by itself, pressing Ctrl+N anywhere in the window runs New_Executed, and any toolbar button with the same command runs the same code. Other built in ones include ApplicationCommands.Open, Save, Copy, Paste, Delete and Help.
Commands are a big topic. You will write your own in Commands and ICommand and add custom shortcuts in Keyboard Shortcuts and Focus. For now, the built in ones are a quick win.
Right click menus with ContextMenu
A context menu is the small menu that pops up when you right click something. Any element can have one through its ContextMenu property:
<ListBox x:Name="ExpenseList">
<ListBox.ContextMenu>
<ContextMenu>
<MenuItem Header="_Rename" Click="Rename_Click" />
<MenuItem Header="_Delete" Click="Delete_Click" />
</ContextMenu>
</ListBox.ContextMenu>
</ListBox>
It holds the same MenuItem elements as the main menu, so everything above still applies. Right click the list and the menu appears where the mouse is. The keyboard works too: the Menu key or Shift+F10 opens it.
ToolBar and ToolBarTray
A ToolBar is a strip of small controls. Place one or more toolbars inside a ToolBarTray, which lines them up and lets the user drag them around.
<ToolBarTray x:Name="MainToolBarTray" DockPanel.Dock="Top">
<ToolBar>
<Button Command="ApplicationCommands.New" ToolTip="New expense (Ctrl+N)">
<TextBlock FontFamily="Segoe Fluent Icons, Segoe MDL2 Assets" Text="" />
</Button>
<Button Click="Delete_Click" ToolTip="Delete selected">
<TextBlock FontFamily="Segoe Fluent Icons, Segoe MDL2 Assets" Text="" />
</Button>
<Separator />
<TextBlock Text="Category:" VerticalAlignment="Center" Margin="4,0" />
<ComboBox x:Name="CategoryBox" Width="110" SelectedIndex="0">
<ComboBoxItem Content="All" />
<ComboBoxItem Content="Food" />
<ComboBoxItem Content="Transport" />
</ComboBox>
</ToolBar>
</ToolBarTray>
Things to notice:
- Controls inside a
ToolBarget a flat toolbar look automatically. The sameButtonlooks different here than in a normal panel. - Icon only buttons need a
ToolTip. Without one, nobody knows what the button does. - The toolbar button uses the same
ApplicationCommands.Newas the menu, so both run the same code.
Overflow
When the window is too narrow, extra toolbar items move into a small drop down arrow at the end of the toolbar. You can control this per item with ToolBar.OverflowMode:
<!-- Never hide this one -->
<Button ToolBar.OverflowMode="Never" Content="Add" />
<!-- Always keep this one in the overflow menu -->
<Button ToolBar.OverflowMode="Always" Content="Export" />
The default is AsNeeded. Lock the most important buttons with Never.
The StatusBar
A StatusBar is a strip at the bottom that holds StatusBarItem elements. It is for short, passive information: the user reads it, but does not usually click it.
<StatusBar DockPanel.Dock="Bottom">
<StatusBarItem>
<TextBlock x:Name="CountText" Text="0 expenses" />
</StatusBarItem>
<Separator />
<StatusBarItem>
<TextBlock x:Name="MessageText" Text="Ready" />
</StatusBarItem>
<!-- HorizontalAlignment="Right" pushes this item to the far right -->
<StatusBarItem HorizontalAlignment="Right">
<TextBlock x:Name="ClockText" />
</StatusBarItem>
</StatusBar>
Give the TextBlock elements names and update them from code whenever something changes:
private void UpdateStatus(string message)
{
CountText.Text = $"{ExpenseList.Items.Count} expenses";
MessageText.Text = message;
}
Setting the text by hand works, but it is easy to forget a spot. In Data Binding Basics you will let the status bar update itself.
Tips
- Keep menus short. If a menu has more than about ten items, split it or move rare items into a sub menu.
- Use the common names. File, Edit, View, Help. People look for “Exit” under File and “About” under Help.
- The toolbar is a shortcut, not a replacement. Every toolbar action should also be in a menu, so keyboard users can reach it.
- Do not put errors only in the status bar. People rarely look down there. Use it for “Saved” or a count, and show real problems in a message box.
- Disable rather than hide. If “Delete” does nothing because nothing is selected, set
IsEnabled="False"so people can still see the action exists. Commands can do this for you automatically, as you will see later.
Quick recap
- Dock a
Menuand aToolBarTrayto the top and aStatusBarto the bottom of aDockPanel. MenuItemhas aHeader, aClickevent, child items,IsCheckable, and anIcon.- An underscore in a header makes an access key.
InputGestureTextonly shows text. - Built in commands such as
ApplicationCommands.Newplus aCommandBindinggive real shortcuts shared by menus and toolbar buttons. ContextMenugives any element a right click menu.ToolBaritems get a flat look and can overflow. Give icon buttons aToolTip.- The
StatusBarshows short facts. Update it from code for now, and with binding soon.
<!-- MainWindow.xaml -->
<Window x:Class="CoinJar.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="CoinJar" Width="640" Height="420">
<!-- Connect built in commands to our code. Ctrl+N and Delete now work. -->
<Window.CommandBindings>
<CommandBinding Command="ApplicationCommands.New" Executed="New_Executed" />
<CommandBinding Command="ApplicationCommands.Delete" Executed="Delete_Executed" />
</Window.CommandBindings>
<DockPanel>
<!-- 1. Menu bar -->
<Menu DockPanel.Dock="Top">
<MenuItem Header="_File">
<MenuItem Command="ApplicationCommands.New" Header="_New expense" />
<Separator />
<MenuItem Header="E_xit" Click="Exit_Click" />
</MenuItem>
<MenuItem Header="_Edit">
<MenuItem Command="ApplicationCommands.Delete" Header="_Delete expense">
<MenuItem.Icon>
<TextBlock FontFamily="Segoe Fluent Icons, Segoe MDL2 Assets" Text="" />
</MenuItem.Icon>
</MenuItem>
</MenuItem>
<MenuItem Header="_View">
<MenuItem Header="Show _toolbar" IsCheckable="True" IsChecked="True"
Click="ShowToolbar_Click" />
</MenuItem>
<MenuItem Header="_Help">
<MenuItem Header="_About CoinJar" Click="About_Click" />
</MenuItem>
</Menu>
<!-- 2. Toolbar -->
<ToolBarTray x:Name="MainToolBarTray" DockPanel.Dock="Top">
<ToolBar>
<Button Command="ApplicationCommands.New" ToolTip="New expense (Ctrl+N)"
ToolBar.OverflowMode="Never">
<TextBlock FontFamily="Segoe Fluent Icons, Segoe MDL2 Assets" Text="" />
</Button>
<Button Command="ApplicationCommands.Delete" ToolTip="Delete selected (Del)">
<TextBlock FontFamily="Segoe Fluent Icons, Segoe MDL2 Assets" Text="" />
</Button>
<Separator />
<TextBlock Text="Category:" VerticalAlignment="Center" Margin="4,0" />
<ComboBox x:Name="CategoryBox" Width="110" SelectedIndex="0">
<ComboBoxItem Content="Food" />
<ComboBoxItem Content="Transport" />
<ComboBoxItem Content="Bills" />
<ComboBoxItem Content="Fun" />
<ComboBoxItem Content="Other" />
</ComboBox>
</ToolBar>
</ToolBarTray>
<!-- 3. Status bar -->
<StatusBar DockPanel.Dock="Bottom">
<StatusBarItem>
<TextBlock x:Name="CountText" Text="0 expenses" />
</StatusBarItem>
<Separator />
<StatusBarItem>
<TextBlock x:Name="MessageText" Text="Ready" />
</StatusBarItem>
</StatusBar>
<!-- 4. Main content fills the rest, with a right click menu -->
<ListBox x:Name="ExpenseList" Margin="8">
<ListBox.ContextMenu>
<ContextMenu>
<MenuItem Command="ApplicationCommands.New" Header="_New expense" />
<MenuItem Command="ApplicationCommands.Delete" Header="_Delete" />
</ContextMenu>
</ListBox.ContextMenu>
</ListBox>
</DockPanel>
</Window>
// MainWindow.xaml.cs
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace CoinJar;
public partial class MainWindow : Window
{
private int _counter;
public MainWindow()
{
InitializeComponent();
}
// Runs for the menu item, the toolbar button, the context menu and Ctrl+N
private void New_Executed(object sender, ExecutedRoutedEventArgs e)
{
_counter++;
var category = ((ComboBoxItem)CategoryBox.SelectedItem).Content;
ExpenseList.Items.Add($"Expense {_counter} ({category})");
UpdateStatus("Added an expense");
}
private void Delete_Executed(object sender, ExecutedRoutedEventArgs e)
{
if (ExpenseList.SelectedItem is null)
{
UpdateStatus("Select an expense first");
return;
}
ExpenseList.Items.Remove(ExpenseList.SelectedItem);
UpdateStatus("Deleted an expense");
}
private void ShowToolbar_Click(object sender, RoutedEventArgs e)
{
var item = (MenuItem)sender;
MainToolBarTray.Visibility = item.IsChecked ? Visibility.Visible : Visibility.Collapsed;
}
private void About_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show("CoinJar 1.0\nA tiny expense tracker.", "About CoinJar");
}
private void Exit_Click(object sender, RoutedEventArgs e)
{
Close();
}
private void UpdateStatus(string message)
{
CountText.Text = $"{ExpenseList.Items.Count} expenses";
MessageText.Text = message;
}
}
Was this explanation helpful?
Your feedback helps us improve the content for everyone.
Thank you for your feedback!
We appreciate you helping us improve.
What could we improve? (Optional)
Read aloud
Ready