WPF Standard Control Demo App › Button
Button Inputs
Button runs an action when it is clicked, through its Click event or a command bound to Command. ClickMode, IsPressed, and the command properties come from its base class ButtonBase.
Esc and Enter: IsCancel and IsDefault
IsCancel makes Esc click the button. The demo app places the button in a UserControl, and it works there: with the focus in a TextBox inside the UserControl, Esc clicked it. Whether the window closes depends on how it was opened. In a window opened with ShowDialog, Esc closed the window and ShowDialog returned false. In a window opened with Show, the button was clicked but the window stayed open, so close such a window yourself.
Keep one IsCancel button per window. With two, Esc clicked neither: it moved the focus to the first one, and pressing it again moved the focus to the second.
IsDefault makes Enter click the button. With the focus in a TextBox, Enter clicked it, and its IsDefaulted was True. With the focus in a TextBox that has AcceptsReturn="True", Enter added a line break and did not click it. With the focus on another button, Enter clicked that button instead, and IsDefaulted was False.
| case | measured |
|---|---|
| in a UserControl, focus in a TextBox: Esc -> clicked / window still open | IsCancel / True |
| Enter -> clicked (IsDefaulted before the key) | IsDefault (True) |
| focus in a TextBox with AcceptsReturn: Enter -> clicked / line breaks in the text | (none) / 1 |
| focus on another Button: Enter -> clicked (IsDefaulted of the IsDefault button) | Other (False) |
| two IsCancel buttons: Esc -> clicked / keyboard focus | (none) / Cancel 1 |
| Esc again -> clicked / keyboard focus | (none) / Cancel 2 |
| IsCancel in a window shown with ShowDialog: Esc -> window closed / ShowDialog returned | True / False |
IsCancel and IsDefault by where the focus is and how the window was opened. Measured on .NET 10 / Windows 11.
When a click happens: ClickMode and IsPressed
ClickMode comes from ButtonBase. With a real mouse, Release (the default) clicked when the mouse button was released. If the pointer was moved off the button before the mouse button was released, IsPressed became False and there was no click. Press clicked when the mouse button went down. Hover clicked as soon as the pointer entered the button, without any mouse button.
The keyboard follows different rules. Space clicked on key up with Release and on key down with Press, and Enter clicked on key down with both. A Hover button cannot be clicked from the keyboard at all: Space and Enter did nothing. Do not use ClickMode="Hover" for buttons that must work from the keyboard.
IsPressed is read-only. It was True while the mouse button was held down on the button and while Space was held down, and False after release. A Hover button was pressed while the pointer was over it, but the keyboard does not press it: IsPressed stayed False while Space was held down.
| case | measured |
|---|---|
| default ClickMode / IsPressed is read-only | Release / True |
| Hover (mouse): pointer enters / pointer leaves | Hover, IsPressed True / (none), IsPressed False |
| Press (mouse): button down / button up | Press, IsPressed True / (none), IsPressed False |
| Release (mouse): button down / button up | (none), IsPressed True / Release, IsPressed False |
| Release (mouse): down on the button, moved off, released outside | IsPressed False after moving off / (none) |
| Release (keyboard): Space down / Space up / Enter down | (none), IsPressed True / Release, IsPressed False / Release |
| Press (keyboard): Space down / Space up / Enter down | Press, IsPressed True / (none), IsPressed False / Press |
| Hover (keyboard): Space down / Space up / Enter down | (none), IsPressed False / (none), IsPressed False / (none) |
ClickMode and IsPressed with a real mouse and with the keyboard. Measured on .NET 10 / Windows 11.
Commands: what enables the button, and when it is checked again
While a bound command’s CanExecute returned false, the button’s IsEnabled was False, even with IsEnabled="True" set on the button. Let CanExecute decide whether the button is enabled.
The demo app’s RelayCommand passes CanExecuteChanged on to CommandManager.RequerySuggested. With that, the button did not follow a change in CanExecute’s result until CommandManager.InvalidateRequerySuggested was called, or until something such as a focus change made WPF check again. That check asks every button again: with 20 buttons bound to the same command, one focus change called CanExecute 20 times, so keep CanExecute cheap.
CommandParameter is the value passed to CanExecute and Execute. The demo app binds it to a TextBox’s Text, after Command. With that XAML, both CanExecute calls during loading received ShowMessageText, never null. A change of the parameter is checked at once: when the text was cleared from code, CanExecute ran once and the button became disabled, with no InvalidateRequerySuggested. Execute received the text at the time of the click. In a list, a button in an item template can pass its item: with CommandParameter="{Binding}", a real click on the button of the second row passed Row 2 to Execute.
| case | measured |
|---|---|
| CanExecute false: IsEnabled / with IsEnabled="True" set | False / False |
| CanExecute turns true: IsEnabled / after InvalidateRequerySuggested | False / True |
| 20 buttons, same command: CanExecute calls per focus change | 20 |
| demo XAML: parameters passed to CanExecute while loading | ShowMessageText, ShowMessageText |
| TextBox.Text set to empty from code: CanExecute calls / IsEnabled | 1 / False |
| TextBox.Text set to Hello, then clicked: parameter of Execute | Hello |
| item template, CommandParameter="{Binding}": real click on the second row: Execute received | Row 2 |
Command and CommandParameter. Measured on .NET 10 / Windows 11.
Content, access keys, and the template
Button derives from ButtonBase, which derives from ContentControl, so its Content can be text, an image, or a panel. The default template shows the content with a ContentPresenter whose RecognizesAccessKey is True: with Content="_Save", S became the access key and the UI Automation name was Save. A button that shows only an image had an empty UI Automation name; give it an AutomationProperties.Name.
The default template has no visual state groups. It changes its look with triggers on IsDefaulted, IsMouseOver, IsPressed, and IsChecked (all true) and on IsEnabled = false.
| case | measured |
|---|---|
| base classes | ButtonBase -> ContentControl |
| default template: VisualStateGroups / triggers | 0 / IsDefaulted=True, IsMouseOver=True, IsPressed=True, IsChecked=True, IsEnabled=False |
| ContentPresenter.RecognizesAccessKey | True |
| UI Automation name: Content "Save" | Save |
| Content "_Save" | Save (access key S) |
| Content is an Image | (empty) |
| an Image with AutomationProperties.Name="Save" | Save |
Base classes, the default template, and UI Automation names. Measured on .NET 10 / Windows 11.
Trying it in the demo app

The Button page of the demo app has sections for IsCancel and IsDefault, ClickMode, IsPressed, Command, and CommandParameter. The “Show Code” link under each section displays its XAML. The following XAML is the CommandParameter section (ButtonUsageControl.xaml), with the styles and the surrounding GroupBoxes left out and the namespace declarations added:
<StackPanel xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<TextBox x:Name="CommandParameterText" Text="ShowMessageText" />
<Button x:Name="ClickWithParameterCommandButton"
Command="{Binding ClickWithParameterCommand}"
CommandParameter="{Binding Text, ElementName=CommandParameterText}"
Content="ShowMessageBox" />
</StackPanel>
The command is defined in the view model (ButtonUsageViewModel.cs):
public ICommand ClickWithParameterCommand { get; } =
new RelayCommand(param => MessageBox.Show(param?.ToString()), null);
Related controls and articles
- RepeatButton — a ButtonBase that keeps clicking while it is held down.
- ToggleButton — a ButtonBase that stays pressed after a click.
- Fixing a RelayCommand Whose CanExecute Does Not Update the Button State in WPF — why a button does not follow
CanExecuteand how to re-query it. - Why WPF Label Hides Underscores and How to Fix It — the access-key behavior that also applies to button content.
Source code and how it was measured
Every behavior on this page was measured by running it on .NET 10 / Windows 11, using ButtonDemoScene in this site’s screenshot tool. Keys were sent through WPF’s input manager, the same path as keys typed on a keyboard, so that IsCancel and IsDefault, which are handled there, respond to them. ClickMode was measured by moving and clicking the real mouse over the measuring window. The click with a CommandParameter from a TextBox was made through UI Automation’s Invoke, and the click on a row’s button in an item template with the real mouse.