【WPF】TextBoxをクリックしたときに全選択させる

C#

こんにちは、働くC#プログラマーのさんさめです。

WPFでTextBoxを扱うとき、
クリックした段階で全選択して欲しいことはよくあります。

例えば、以下のユースケースが考えられます。

  • 数値入力など、
    そこを選択する以上は必ず新規入力がしたい場合
    • 最初から全選択されていればすぐ入力を開始できる
  • テキストをコピーしたい場合
    • 最初から全選択されていればすぐCtrl+Cを押せる

ところが、WPFのTextBoxには
クリック時に全選択するという機能はありません

だからこそ、このちょっと気の利いた機能を実装することで、
「あ、使いやすいなこのアプリケーション」
と思ってもらえる可能性が高まります。

というわけで、
本記事では添付プロパティを使って
マウスクリック時に全選択させる挙動を
上乗せする方法について解説します。

スポンサーリンク

添付プロパティを使ってマウスクリック時に全選択

さっそくですが、以下のようなクラスを作ります。

やっていることは以下の通りです。

  1. フォーカス取得時に全選択するかどうかの添付プロパティを定義
  2. 添付プロパティが設定されたときに2種のイベントを購読
  3. イベント内でフォーカス取得時に全選択するように
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にする添付プロパティ
を使ってユーザー体験を向上させましょう。

さんさめ
さんさめ

構成がほとんど同じや

コメント

タイトルとURLをコピーしました