📝

【WPF】数値しか入力できないTextBox

2024/02/25に公開

数値しか入力できないTextBoxの作り方紹介。ポイントは

  1. TextBoxを継承して作成する
  2. InputMethod.SetIsInputMethodEnabledを利用してIMEをOFFにする(アンフォーカスするとIMEの状態は元に戻る)
  3. DataObject.AddPastingHandlerでペーストをハンドルして数値チェックする
  4. OnPreviewTextInputをオーバーライドして数値チェックをする
  5. OnPreviewKeyDownをオーバーライドして、スペースを弾く

3, 4の際に、新しい値・カーソル位置・現在値から、入力後の値を想定してでチェックする必要があります。
ただOnPreviewTextInputだとスペースがなぜか取れないので、それはOnPreviewKeyDownで処理します。
OnPreviewKeyDownでペースト以外処理することもできるんですが、キー入力判定とか、細かなバグが入りそうなので
OnPreviewTextInputと併用しました。

というわけで、下記に動くコードを置いておきました。

またはこちらを参照してください。コメント見ればわかると思います。

using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;

namespace WpfNumericTextBoxStudy;

/// <summary>
/// int型のみを入力可能とするテキストボックス
/// </summary>
public class IntTextBox : TextBox
{
    /// <summary>
    /// コンストラクター
    /// </summary>
    public IntTextBox()
    {
        // IMEを無効にする
        InputMethod.SetIsInputMethodEnabled(this, false);

        // ペーストイベントをハンドリング
        DataObject.AddPastingHandler(this, OnPaste);
    }

    /// <summary>
    /// スペースキーが押された場合、イベントをキャンセルします。
    /// </summary>
    /// <param name="e"></param>
    protected override void OnPreviewKeyDown(KeyEventArgs e)
    {
        base.OnPreviewKeyDown(e);

        // スペースキーが押された場合、イベントをキャンセルします。
        if (e.Key == Key.Space)
        {
            e.Handled = true;
        }
    }

    /// <summary>
    /// テキストボックスに入力されたテキストを、値の反映前に検証します。
    /// </summary>
    /// <param name="e"></param>
    protected override void OnPreviewTextInput(TextCompositionEventArgs e)
    {
        base.OnPreviewTextInput(e);

        // e.Textには、入力されたテキストが入っています。
        // 入力されたテキストから、反映後のテキストを予測します。
        var afterText = GetAfterText(e.Text);

        if (!IsInt(afterText))
        {
            // 反映後の値がint型の入力値ではない場合、イベントをキャンセルします。
            e.Handled = true;
        }
    }
    
    /// <summary>
         /// ペーストされたテキストを、値の反映前に検証します。
         /// </summary>
         /// <param name="sender"></param>
         /// <param name="e"></param>
    private void OnPaste(object sender, DataObjectPastingEventArgs e)
    {
        if (e.DataObject.GetDataPresent(typeof(string)))
        {
            // ペーストされたデータが文字列として取得できる場合
            // ペーストされたテキストを取得します。
            var text = (string?)e.DataObject.GetData(typeof(string)) ?? string.Empty;

            // ペーストされたテキストから、反映後のテキストを予測します。
            var afterText = GetAfterText(text);

            if (!IsInt(afterText))
            {
                // 反映後の値がint型の入力値ではない場合、イベントをキャンセルします。
                e.CancelCommand();
            }
        }
        else
        {
            // ペーストされたデータが文字列として取得できない場合、イベントをキャンセルします。
            e.CancelCommand();
        }
    }

    /// <summary>
    /// テキストがint型の値かどうかを判定します。
    /// </summary>
    /// <param name="text"></param>
    /// <returns></returns>
    private bool IsInt(string text)
    {
        if ("-".Equals(text))
        {
            // マイナス記号のみの場合はtrueを返す
            return true;
        }

        // まずint形にパースして確認する。
        if (int.TryParse(text, out var intValue))
        {
            // 01などを除外するために、パースした値を文字列に変換して、入力値と一致するかどうかを確認する。
            return string.Equals(text, intValue.ToString());
        }
        return false;
    }

    /// <summary>
    /// ペースト後のテキストを予測します。
    /// </summary>
    /// <param name="text"></param>
    /// <returns></returns>
    private string GetAfterText(string text)
    {
        // ペースト後のテキストを予測(例えば、数値以外の文字が含まれる場合はペーストをキャンセル)
        return Text.Substring(0, SelectionStart)
               + text
               + Text.Substring(SelectionStart + SelectionLength);
    }
}

Discussion