こんにちは、働くC#プログラマーのさんさめです。
WPFでスペースキーに
処理をアサインした経験はあるでしょうか。
WPFでスペースキーやEnterキーをよく押す
アプリケーションを開発する場合、
ボタンにキー入力が吸われてしまう可能性を
考慮に入れておく必要があります。
というのも、
KeyBindingでSpaceを使っているコントロール内にボタンを置くと
ボタンをクリックした時に
そのボタンにフォーカスが合った状態になり、
スペースキーやEnterキーの入力を
優先して処理してしまうのです。

この挙動は、
他のコントロールにフォーカスが移るまで、
ずっと続きます。
すると、どうなるでしょうか。
「なんかたまにスペースキーが効かないアプリ」
とユーザーに烙印を押されてしまうわけです。

それだけはなんとしても避けたい
この状態に陥らないようにする方法には、
2つの異なるアプローチがあるので、
それぞれを説明していきたいと思います。
KeyBindingではなくPreviewKeyDownイベントを使う
コードビハインドで
PreviewKeyDownイベントを購読すると、
ボタンまでイベントが伝搬する前に
キー入力を拾うことができます。
コードサンプルは以下のようになります。
protected override void OnPreviewKeyDown(KeyEventArgs e)
{
base.OnPreviewKeyDown(e);
if (e.Key == Key.Space)
{
// 実行したい処理
TextUpdateCommand.Execute(null, null);
e.Handled = true;
}
}
この方法には欠点もあります。
PreviewKeyDownでハンドリングしてしまうと、
あらゆるスペースキー入力を全て購読してしまうため、
スペースを使いたい別のケースの対処が難しいです。
有名どころとしては、
テキストボックスに半角スペースを入力できなくなります。

一応、日本語入力なら変換とかはできる。一応
また、コードビハインドに書く場合、
そのままではMVVMに適さない
ということに注意しましょう。

MVVMに適さないってどういうこと?
例えば、MVVMでよくあるICommandを使っている
ケースを考えてみます。
実行したい処理の指定をICommandを
Bindingする形で行っていた場合、
Viewはどんな処理を実行すべきなのか分かりません。
以下のように書けば動くことは動きますが、
ViewがViewModelを知っている
(=特定のViewModelでないと正常動作しない)
ということになります。
protected override void OnPreviewKeyDown(KeyEventArgs e)
{
base.OnPreviewKeyDown(e);
if (e.Key == Key.Space)
{
(this.DataContext as MyViewModel)?.HogeCommand.Execute(null);
e.Handled = true;
}
}
PreviewKeyDownイベントとICommandをつなぐマークアップ拡張
などを併用するとMVVMパターンを崩さずに実装できます。
ボタンのFocusableフラグをfalseにする
もう一つ、簡単な方法として、
Focusableプロパティをfalseにする、
という手段があります。
マウスクリックでボタンを押すことはできるけど、
フォーカスを当てることはさせない。
という挙動になります。

フォーカスしているボタンは破線で囲われます。
Focusable=Falseのボタンは何度クリックしても、
Tabキーを押してフォーカス移動をしようとしても、
決してフォーカス状態にならなくなります。
キー操作を吸ってほしくないボタンに対して、
片っ端からFocusableプロパティをfalseにしてやれば、
Enterやスペースが吸われることはなくなります。
大量にある場合はStyleで設定してしまっても良いですね。
また、ボタンだけでなく、
チェックボックスなどにも同様の対処が行えます。
チェックボックスもスペースキーには、
チェックのトグル操作がアサインされているため、
Focusableをfalseにしておくと、
マウスクリックのみに反応するチェックボックスに変更できます。
Enterやスペースキーに処理をアサインしない
Enterやスペースキーは、
既定でハンドリングするコントロールが多いため、
そもそもそんなキーに処理をアサインしないというのも1つの手です。
他のコントロールに吸われる可能性に気を使いながら、
それでもアサインをするほど
Enterやスペースキーが最も直感的なのか…
再検討してみてもいいかもしれませんね。

身も蓋もないですが…
それだけEnterやスペースキーは罠が多いのです
まとめ
まとめです。
- Enterキーやスペースキーに処理をアサインするときは
意図せずボタンに操作を吸われないように気を付ける - PreviewKeyDownイベントを購読すれば
全てのコントロールより先にキー入力を受け取れる - Focusable=Falseにしておけば
フォーカスを取得しなくなるので、
結果としてキー操作を吸うこともなくなる
最後までお読みいただきありがとうございました。
関連記事
「Enterキーに処理をアサインしたい」
これはそもそもCommandBindingなどを使わずとも
簡単に行う方法があります。
【WPF】ボタンの処理を手軽にEnterとEscにキーアサインする方法
という記事で詳しく解説しています。
…この方法を使っていても、
他のボタンにフォーカスが当たっていると、
ボタンの押下が優先されてしまいますが。
コメント