こんにちは、働くC#プログラマーのさんさめです。
WPFのTextBoxには、
1つ、とても直感的じゃない挙動があります。
Enterキーを押してもバインディング先に変更通知が飛ばない
のです。
これを知らずにTextBoxを利用していると、
知らない間に使い勝手が悪い認定を
されてしまったりします。
「Enter押したのに反応しなかったんだけど!」
って必ず言われます。対策必須です。
逆に言えば、Enterで確定できるようにしておけば、
「直感的に使えるツールだなぁ」
という評価に一歩前進できます。
本記事では、TextBoxでEnterキーを押したときに、
バインディングを解決させる方法について解説します。
TextBoxがEnterキーを押しても無反応な理由
MVVMでTextBoxを使うのであれば、
<TextBox Text="{Binding Name}"/>
と書きますよね。
これ自体は何も間違っていないのですが、
これでは、TextBoxがバインディング先に変更通知を飛ばすのは、
Tabキーやマウス操作などによってフォーカスを失ったときです。
Enterキーではフォーカスが失われないため反応しません。
実際に挙動を見てみましょう。
Enterキーには無反応です。
この変更通知のタイミングは、
UpdateSourceTriggerプロパティによって変えられます。
たとえば、以下のように設定すると
文字を打つたびに変更通知が行われます。
<TextBox Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}"/>
使い方によっては十分ですが、
これでは逆に変更通知が飛び過ぎで困ることもあります。
そこで、添付プロパティを活用して、
Enterキー押下時に変更通知を飛ばすようにしてみましょう。
添付プロパティを使ってEnterで確定できるTextBoxにする
以下のようなクラスを作ります。
やっていることは、以下の通りです。
- 動作モードであるenumの定義
- enumを添付プロパティとして設定するための定義
- 添付プロパティが設定されたときに
PreviewKeyDownイベントを購読 - 押されたキーがEnterだったら、
Bindingを取得しUpdateSource(変更通知)を呼び出す
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
namespace TextBoxBehaviorSample
{
// 1.enumで動作モードの定義
public enum EnterBehaviorMode
{
None,
UpdateSource,
UpdateSourceAndSelectAll
}
public static class TextBoxAttachment
{
// 2.enumを添付プロパティとして設定するための定義
public static EnterBehaviorMode GetEnterDownBehavior(DependencyObject obj)
{
return (EnterBehaviorMode)obj.GetValue(EnterDownBehaviorProperty);
}
public static void SetEnterDownBehavior(DependencyObject obj, EnterBehaviorMode value)
{
obj.SetValue(EnterDownBehaviorProperty, value);
}
// Using a DependencyProperty as the backing store for EnterDownBehavior. This enables animation, styling, binding, etc...
public static readonly DependencyProperty EnterDownBehaviorProperty =
DependencyProperty.RegisterAttached("EnterDownBehavior", typeof(EnterBehaviorMode), typeof(TextBoxAttachment), new PropertyMetadata(EnterBehaviorMode.None, (d, e) =>
{
if (!(d is TextBox tb)) { return; }
if (!(e.NewValue is EnterBehaviorMode mode)) { return; }
// 3.添付プロパティが設定されたときにイベント購読
if (mode != EnterBehaviorMode.None)
{
tb.PreviewKeyDown += OnTextBoxKeyDown;
}
else
{
tb.PreviewKeyDown -= OnTextBoxKeyDown;
}
}));
private static void OnTextBoxKeyDown(object sender, System.Windows.Input.KeyEventArgs e)
{
// 4.Enterキーが押されたときに
if (e.Key != System.Windows.Input.Key.Enter)
{
return;
}
if (!(sender is TextBox tb)) { return; }
var mode = GetEnterDownBehavior(tb);
if (mode == EnterBehaviorMode.None) { return; }
// Bindingを取得してUpdateSource呼出し
var be = BindingOperations.GetBindingExpression(tb, TextBox.TextProperty);
be?.UpdateSource();
// おまけ。入力中の文字を全選択するモード
if (mode == EnterBehaviorMode.UpdateSourceAndSelectAll)
{
tb.SelectAll();
}
e.Handled = true;
}
}
}
利用法はとっても簡単です。
次のようにTextBoxに設定します。
<TextBox Text="{Binding Name}"
local:TextBoxAttachment.EnterDownBehavior="UpdateSource"/>
これだけで、Enterキーを押したタイミングで
通知が飛ぶようになります。
単にEnterに反応させたいだけなら、
添付ビヘイビアの利用も有り
Enterキーで確定の挙動を確認してみる
それでは、先ほどのアプリケーションに
この添付プロパティを設定してみて、
挙動を確認してみましょう。
いい感じにEnterを押したときに、
「入力した文字」の部分に反映されていますね。
おまけ。変更通知とともに入力した文字を全選択するモード
ちなみに、上記のクラスではおまけで、
Enterキーで確定するだけでなく
全選択するモードも作ってみました。
<TextBox Text="{Binding Rate}"
local:TextBoxAttachment.EnterDownBehavior="UpdateSourceAndSelectAll"/>
…え?全選択されると何がうれしいの?
やっぱりそう思いますよね。
というわけで、
動作がイメージできるサンプルを用意しました。
このように、数値のシミュレーションなど、
ユーザーが様々な入力パターンを試したい時には、
入力→Enter→次の入力→Enter→…
とテンポよく入力できると便利なのです。
毎回前の値を消すのって結構手間ですからね。
そんな時のためのモードです。
だまされたと思って一度試してみるとその良さに気づけます。
まとめ
まとめです。
- WPFのTextBoxは標準ではEnterキーを押しても無反応
- UpdateSourceTriggerを変えれば
文字入力されるたびに変更通知は飛ばせる - 添付プロパティによって
Enterキーで変更通知を飛ばす方法を紹介 - Enterのたびに通知とともに
全選択し直す挙動も使い時を選べば便利
最後までお読みいただきありがとうございました。
関連記事
Enterのたびに全選択する挙動を実装すると、
他のコントロールから
TextBoxにフォーカスが移った時も
全選択して欲しくなります。
【WPF】TextBoxをクリックしたときに全選択させる
では、マウスクリック時やTabキーによるフォーカス取得時に、
テキストを全選択する方法について解説しています。
コメント