こんにちは、働くC#プログラマーのさんさめです。
WPFでTextBoxを扱うとき、
クリックした段階で全選択して欲しいことはよくあります。
例えば、以下のユースケースが考えられます。
- 数値入力など、
そこを選択する以上は必ず新規入力がしたい場合- 最初から全選択されていればすぐ入力を開始できる
 
 - テキストをコピーしたい場合
- 最初から全選択されていればすぐCtrl+Cを押せる
 
 

ところが、WPFのTextBoxには
クリック時に全選択するという機能はありません。
だからこそ、このちょっと気の利いた機能を実装することで、
「あ、使いやすいなこのアプリケーション」
と思ってもらえる可能性が高まります。
というわけで、
本記事では添付プロパティを使って
マウスクリック時に全選択させる挙動を
上乗せする方法について解説します。
添付プロパティを使ってマウスクリック時に全選択
さっそくですが、以下のようなクラスを作ります。
やっていることは以下の通りです。
- フォーカス取得時に全選択するかどうかの添付プロパティを定義
 - 添付プロパティが設定されたときに2種のイベントを購読
 - イベント内でフォーカス取得時に全選択するように
 
public static class TextBoxAttachment
{
    // 1.添付プロパティの定義
    public static bool GetIsSelectAllOnGotFocus(DependencyObject obj)
    {
        return (bool)obj.GetValue(IsSelectAllOnGotFocusProperty);
    }
    public static void SetIsSelectAllOnGotFocus(DependencyObject obj, bool value)
    {
        obj.SetValue(IsSelectAllOnGotFocusProperty, value);
    }
    // Using a DependencyProperty as the backing store for GotFocusBehavior.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty IsSelectAllOnGotFocusProperty =
        DependencyProperty.RegisterAttached("IsSelectAllOnGotFocus", typeof(bool), typeof(TextBoxAttachment), new PropertyMetadata(false, (d, e) =>
        {
            if (!(d is TextBox tb)) { return; }
            if (!(e.NewValue is bool isSelectAll)) { return; }
            tb.GotFocus -= OnTextBoxGotFocus;
            tb.PreviewMouseLeftButtonDown -= OnMouseLeftButtonDown;
            if (isSelectAll)
            {
                // 2.イベント購読
                tb.GotFocus += OnTextBoxGotFocus;
                tb.PreviewMouseLeftButtonDown += OnMouseLeftButtonDown;
            }
        }));
    private static void OnTextBoxGotFocus(object sender, RoutedEventArgs e)
    {
        if (!(sender is TextBox tb)) { return; }
        var isSelectAllOnGotFocus = GetIsSelectAllOnGotFocus(tb);
        // フォーカス取得時に全選択する
        if (isSelectAllOnGotFocus)
        {
            tb.SelectAll();
        }
    }
    private static void OnMouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
    {
        if (!(sender is TextBox tb)) { return; }
        // おまじない(後述)
        if (tb.IsFocused) { return; }
        tb.Focus();
        e.Handled = true;
    }
}利用法はとっても簡単です。
次のようにTextBoxに設定します。
<TextBox Text="500" 
         local:TextBoxAttachment.IsSelectAllOnGotFocus="True"/>これだけで、
クリック時やTabキーによるフォーカス移動時に、
TextBoxのテキストが全選択されるようになります。
マウスクリック時に全選択の挙動を確認してみる
実際に設定してみて、
挙動を確認してみましょう。

クリック時、Tabキーによるフォーカス移動時に、
テキストが全選択されています。
単にGotFocus時にSelectAllするだけではうまくいかない
さて、コードを注意深く読んだ方は、
次のような疑問を持ったかもしれません。

さっきのコードサンプル、
GotFocusのイベント購読だけで十分なのでは?
サンプルコードでは、GotFocusイベントだけでなく、
PreviewMouseLeftButtonDownイベントも購読していました。
private static void OnMouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
    if (!(sender is TextBox tb)) { return; }
    // おまじない(後述)
    if (tb.IsFocused) { return; }
    tb.Focus();
    e.Handled = true;
}これ、実は省いてしまうと、
「クリック時に一瞬全選択されてすぐ剥がれる」
という挙動になってしまうのです。
(※Tabキーによるフォーカス移動時はうまくいきます)
せっかくなので、
この処理を外した例を見てみましょう。

ご覧の通り、
Tabキーによるフォーカス移動では
問題なく動作しますが、
クリック時にはうまくいっていません。

Dispatcher.BeginInvokeを使って、
一周遅らせてからSelectAllをすればいいんじゃない?
Dispatcher.BeginInvokeも試してみましたが、
マウスボタンを押してからゆ~っくり離すと、
やっぱり剥がれてしまうんですね。
どうやらマウスのボタンを離した時に
キャレットを動かす処理が行われてしまっているみたいです。
というわけで、
PreviewMouseLeftButtonDownイベントを購読することで、
マウスのボタンを押した段階でフォーカスを取得させて、
ハンドリングしたことにしてしまう。
というおまじないが必要になるのでした。
まとめ
まとめです。
- WPFのTextBoxには、クリック時に全選択する機能はない
 - 添付プロパティによって
クリック時に全選択する方法を紹介 - ついでに、Tabキーのときも全選択される
 - GotFocusイベントを購読するだけでは
うまくいかなかった例を紹介 
最後までお読みいただきありがとうございました。
関連記事
クリック時に全選択されて
テンポよく新規入力ができるようになっても、
Enterキーでサクサク確定できないと
良いテンポが途切れユーザー体験は悪くなります。
しかし、そんな機能はやっぱりTextBoxにはありません。
【WPF】Enterキーで確定できるTextBoxにする添付プロパティ
を使ってユーザー体験を向上させましょう。

構成がほとんど同じや
 
コメント