WPF 標準コントロールデモアプリ › Button

Button Inputs

Button は、クリックされると Click イベントか Command にバインドしたコマンドで処理を実行するボタンです。ClickMode、IsPressed、コマンド関連のプロパティは基底クラスの ButtonBase が持っています。

Esc と Enter:IsCancel と IsDefault

IsCancel は、Esc でボタンを押せるようにします。デモアプリはボタンを UserControl の中に置いており、そこでも動きます。UserControl の中の TextBox にフォーカスがある状態で、Esc でボタンが押されました。ウィンドウが閉じるかどうかは、ウィンドウの開き方で変わります。ShowDialog で開いたウィンドウは Esc で閉じ、ShowDialog は false を返しました。Show で開いたウィンドウでは、ボタンは押されましたがウィンドウは開いたままだったので、自分で閉じます。

IsCancel のボタンは 1 つのウィンドウに 1 つにします。2 つあると、Esc ではどちらも押されませんでした。フォーカスが 1 つ目のボタンへ移り、もう一度押すと 2 つ目へ移りました。

IsDefault は、Enter でボタンを押せるようにします。TextBox にフォーカスがあると Enter で押され、そのときの IsDefaulted は True でした。AcceptsReturn="True" の TextBox にフォーカスがあると、Enter は改行になり、ボタンは押されませんでした。ほかのボタンにフォーカスがあると、Enter ではフォーカスのあるボタンが押され、IsDefaulted は False でした。

条件 計測値
UserControl の中、TextBox にフォーカス: Esc で押されたボタン / ウィンドウが開いたままか IsCancel / True
Enter で押されたボタン(キーを押す前の IsDefaulted) IsDefault(True)
AcceptsReturn の TextBox にフォーカス: Enter で押されたボタン / 文字列の改行の数 (なし) / 1
別の Button にフォーカス: Enter で押されたボタン(IsDefault のボタンの IsDefaulted) Other(False)
IsCancel のボタンが 2 つ: Esc で押されたボタン / キーボードフォーカス (なし) / Cancel 1
もう一度 Esc: 押されたボタン / キーボードフォーカス (なし) / Cancel 2
ShowDialog で開いたウィンドウの IsCancel: Esc でウィンドウが閉じたか / ShowDialog の戻り値 True / False

フォーカスの位置とウィンドウの開き方ごとの IsCancel と IsDefault。.NET 10 / Windows 11 で計測。

どの時点でクリックになるか:ClickMode と IsPressed

ClickMode は ButtonBase のプロパティです。実際のマウスで試すと、既定値の Release はマウスボタンを離した時点でクリックになりました。離す前にポインターをボタンの外へ出すと、IsPressed が False になり、クリックになりませんでした。Press はマウスボタンを押した時点でクリックになりました。Hover は、マウスボタンを押さなくても、ポインターがボタンに入った時点でクリックになりました。

キーボードでは規則が違います。Space は Release ならキーを離した時点、Press ならキーを押した時点でクリックになり、Enter はどちらもキーを押した時点でクリックになりました。Hover のボタンはキーボードでは押せず、Space でも Enter でも何も起きませんでした。キーボードで操作するボタンには ClickMode="Hover" を使いません。

IsPressed は読み取り専用です。ボタンの上でマウスボタンを押している間と、Space を押している間は True で、離すと False でした。Hover のボタンは、ポインターが上にある間押された状態でしたが、キーボードでは押された状態にならず、Space を押している間も IsPressed は False のままでした。

条件 計測値
ClickMode の既定値 / IsPressed が読み取り専用か Release / True
Hover(マウス): ポインターが入る / 出る Hover、IsPressed True / (なし)、IsPressed False
Press(マウス): ボタンを押す / 離す Press、IsPressed True / (なし)、IsPressed False
Release(マウス): ボタンを押す / 離す (なし)、IsPressed True / Release、IsPressed False
Release(マウス): ボタンの上で押し、外へ動かして離す 外へ動かした後の IsPressed False / (なし)
Release(キーボード): Space を押す / Space を離す / Enter を押す (なし)、IsPressed True / Release、IsPressed False / Release
Press(キーボード): Space を押す / Space を離す / Enter を押す Press、IsPressed True / (なし)、IsPressed False / Press
Hover(キーボード): Space を押す / Space を離す / Enter を押す (なし)、IsPressed False / (なし)、IsPressed False / (なし)

実際のマウスとキーボードでの ClickMode と IsPressed。.NET 10 / Windows 11 で計測。

コマンド:何がボタンを有効にし、いつ問い合わせ直されるか

バインドしたコマンドの CanExecute が false を返す間、ボタンの IsEnabled は False でした。ボタンに IsEnabled="True" を設定しても同じです。有効・無効は CanExecute で決めます。

デモアプリの RelayCommand は、CanExecuteChanged を CommandManager.RequerySuggested に委ねています。この場合、CanExecute の結果が変わっても、CommandManager.InvalidateRequerySuggested を呼ぶか、フォーカスの移動などで WPF が問い合わせ直すまで、ボタンは変わりませんでした。この問い合わせはボタンごとに行われます。同じコマンドをバインドしたボタンが 20 個あると、フォーカスの移動 1 回で CanExecute が 20 回呼ばれました。CanExecute は軽くします。

CommandParameter は、CanExecute と Execute に渡す値です。デモアプリは、Command の後に TextBox の Text へバインドしています。この XAML では、読み込み時の 2 回の CanExecute にはどちらも ShowMessageText が渡り、null は渡りませんでした。パラメーターが変わると、すぐに問い合わせ直されます。コードからテキストを空にすると、InvalidateRequerySuggested を呼ばなくても CanExecute が 1 回呼ばれ、ボタンは無効になりました。Execute には、クリックした時点のテキストが渡りました。一覧では、項目のテンプレートに置いたボタンでその項目を渡せます。CommandParameter="{Binding}" にすると、2 行目のボタンを実際にクリックしたとき、Execute は Row 2 を受け取りました。

条件 計測値
CanExecute が false: IsEnabled / IsEnabled="True" を指定したボタン False / False
CanExecute が true に変わる: IsEnabled / InvalidateRequerySuggested の後 False / True
同じコマンドのボタンが 20 個: フォーカスが 1 回移るごとの CanExecute の呼び出し回数 20
デモの XAML: 読み込み中に CanExecute へ渡された引数 ShowMessageText, ShowMessageText
コードから TextBox.Text を空にする: CanExecute の呼び出し回数 / IsEnabled 1 / False
TextBox.Text を Hello にしてクリック: Execute の引数 Hello
項目のテンプレート、CommandParameter="{Binding}": 2 行目を実際にクリック: Execute が受け取った値 Row 2

Command と CommandParameter。.NET 10 / Windows 11 で計測。

内容、アクセスキー、テンプレート

Button は ButtonBase を継承し、ButtonBase は ContentControl を継承しています。そのため Content には文字列、画像、パネルを置けます。既定のテンプレートは内容を ContentPresenter で表示し、その RecognizesAccessKey は True です。Content="_Save" では S がアクセスキーになり、UI オートメーションの名前は Save でした。画像だけのボタンでは UI オートメーションの名前が空だったので、AutomationProperties.Name を付けます。

既定のテンプレートには表示状態のグループ(VisualStateGroup)がありません。見た目は、IsDefaulted・IsMouseOver・IsPressed・IsChecked(いずれも true)と IsEnabled = false のトリガーで切り替えています。

条件 計測値
基底クラス ButtonBase -> ContentControl
既定のテンプレート: VisualStateGroups の数 / トリガー 0 / IsDefaulted=True, IsMouseOver=True, IsPressed=True, IsChecked=True, IsEnabled=False
ContentPresenter.RecognizesAccessKey True
UI オートメーションの名前: Content が "Save" Save
Content が "_Save" Save(アクセスキー S)
Content が Image (空)
AutomationProperties.Name="Save" を付けた Image Save

基底クラス、既定のテンプレート、UI オートメーションの名前。.NET 10 / Windows 11 で計測。

デモアプリで試す

デモアプリの Button のページ。左にコントロールの一覧、右に最初の節の IsCancel / IsDefault

デモアプリの Button のページには、IsCancel と IsDefault、ClickMode、IsPressed、Command、CommandParameter の欄があります。各欄の下の「Show Code」リンクで、その欄の XAML を表示できます。次の XAML は、CommandParameter の欄(ButtonUsageControl.xaml)から、スタイルと周囲の GroupBox を省き、名前空間の宣言を加えたものです。

<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>

コマンドはビューモデル(ButtonUsageViewModel.cs)で定義しています。

public ICommand ClickWithParameterCommand { get; } =
    new RelayCommand(param => MessageBox.Show(param?.ToString()), null);

関連するコントロールと記事

ソースコードと計測の方法

このページの挙動の記述は、すべて .NET 10 / Windows 11 で実際に動かして確かめたものです。計測には、このサイトのスクリーンショット生成ツールの ButtonDemoScene を使いました。IsCancel と IsDefault は WPF の入力管理(InputManager)の中で処理されるため、キーはキーボードから打ったときと同じく InputManager を通して送りました。ClickMode は、計測用のウィンドウの上で実際のマウスを動かし、クリックして確かめました。TextBox の文字を CommandParameter で渡すクリックは UI オートメーションの Invoke で、項目のテンプレートの行のボタンのクリックは実際のマウスで行いました。

GitHub で Button のソースコードを見る →