こんにちは、働くC#プログラマーのさんさめです。
キーボードによるテキスト入力が仕様に入る
アプリケーションを作るとき、
「キーボードだけでもスムーズに操作できるか?」
は気にかけておきたい点の1つです。
特に、
- パスワード入力フォーム
- 「名前を付けて保存」
といった小規模なウィンドウにおいては、
タブキーでサクサク入力できると
使っている側も「使いこなしている感」が出て嬉しいものです。

しかし、WPFでは、タブキーを押した時は
原則xamlで上に書いたコントロールから順に
フォーカスが移っていきます。
そのため、DockPanelなどを使っていると、
タブによるフォーカス移動が直感的でなくなっていることがあります。
また、ユーザーコントロールや
カスタムコントロールを使っていると、
まったく意味のないところにフォーカスが当たったりします。
本記事では、
TabIndexプロパティとIsTabStopプロパティを駆使して、
タブキーによるフォーカス遷移順序を
コントロールする方法について解説します。
TabIndexを付与すると値の小さい順にフォーカスが遷移する
早速ですが、TabIndexという、
そのものずばりなプロパティがあります。
これを設定することで、フォーカス順を制御できます。
設定は整数(int)で行うようになっており、
値の小さい順にフォーカスが遷移します。
それでは、TabIndexを全く設定していない場合に、
どれくらい遷移順序がまずくなるのか、
具体例を見てみましょう。
以下はDockPanelをふんだんにつかって、
かつ、TabIndexを何も設定していないサンプルです。
<Window x:Class="FocusManagerSample.TabSampleWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="TabSampleWindow" Height="220" Width="300">
<DockPanel>
<WrapPanel DockPanel.Dock="Bottom" Margin="4">
<Button Content="OK" Width="40"/>
<Button Content="Cancel" Width="40" Margin="4 0 0 0"/>
</WrapPanel>
<Menu DockPanel.Dock="Top">
<MenuItem Header="ファイル"/>
<MenuItem Header="編集"/>
</Menu>
<Button Content="メニュー"
DockPanel.Dock="Left"/>
<StackPanel>
<TextBlock Text="メインコンテンツ"/>
<TextBox Text="DockPanelはタブキーの遷移がぐちゃっとしがち"/>
</StackPanel>
</DockPanel>
</Window>
実行結果は次のようになります。
Tabを押してフォーカスが当たったコントロールを、
マウスで追いかけています。
(gifアニメです)

最初にタブキーを押した時に、
いきなり下のボタンにフォーカスが移ってしまっていますね。
その後は比較的直感的に遷移しています。
では、TabIndexを設定してみましょう。
先ほどのサンプルの、フォーカスが当たっていたコントロールに
TabIndexを設定してみます。

そして、実行してみましょう。
再びgifアニメです。

この通り、上部メニューの「ファイル」
に最初にフォーカスが当たるようになりました。
既定値がintの最大値であることを利用して設定するコントロールを減らす
ただ、これだとフォーカスを持つすべてのコントロールに
逐一設定していかないといけないため、
少し面倒ですね。
そこで、TabIndexの既定値がint.MaxValueであることを利用します。
既定値はint.MaxValueとなっているため、
設定しているコントロールは、
必ず設定していないコントロールよりも先に、
フォーカスが巡ってくるということになります。
また、同値ならばxamlに配置した通りにフォーカスが遷移します。
…ということは、最初から直感的だったコントロールたちには、
別々の値を細かく設定する必要はありませんね。
上記を考慮して、
次のように設定しても全く同じ動作になります。

まず、単純に設定箇所が減りましたね。
また、TabIndexの設定値を全部0にしたことで、
何の数字を振るべきか考える必要が無くなりました。
新しいコントロールを追加することになっても、
xamlの規定動作に任せておけばいいことも多いため、
既存のTabIndexを振り直しする必要が無いのは嬉しいですね。
タブで遷移する必要がないコントロールはIsTabStopをfalseにしておく
さて、先ほどのTabIndexは、
単に順序を制御するのみでした。
しかし、実際にアプリケーションを動かしてみると、
「別にここにタブでフォーカス当たる必要ないな」
というコントロールが見つかることもあるでしょう。
これには「慣れたら触る必要のないコントロール」
などが該当するかと思います。
例えば、「ヘルプを表示するボタン」は、
初めてそのアプリを触る人には役に立つかもしれませんが
日常的に活用している人には無縁ですね。
さて、
そんな時は、IsTabStopというプロパティが役に立ちます。
その名の通り、
タブキーを押した時にフォーカスが移るかどうかを設定できます。
既定値はTrueです。
さて、先ほどのウィンドウで、
たとえば上部のメニューバーにはタブが移る必要が無かったとしましょう。
その場合は以下のように設定します。

これを実行すると、次のようになります。

タブキーを押してもメニューバーにフォーカスが移らなくなりました。
タブキーによるフォーカス遷移が本質的な部分にのみ
行われることによって、
ユーザーはより効率的なアプリ操作ができるようになります。
まとめ
まとめです。
- キーボード入力のあるアプリは
「キーボードだけで操作できるか」を気に掛けると良い - TabIndexを使うと、タブキー押下時の
フォーカス遷移の順序をカスタムできる - IsTabStopを使うと、タブキー押下時に
フォーカスされる要素を変更できる - タブキーで次々に入力すべき個所を
入力可能にするとテンポよくアプリ操作が可能になる
最後までお読みいただき、ありがとうございました。
関連記事
キーボード入力を伴う小規模ウィンドウでは、
EnterキーやEscキーにショートカットを割り当てることも
気の利かせ方の一つの手段となります。
【WPF】ボタンの処理を手軽にEnterとEscにキーアサインする方法
を使えば、サクッとEnterやEscに処理をアサインできます。
コメント