【C#】テキストボックスの入力形式制限(リアルタイム版)

TextChangedは使いたくないな~~~!?!?!

 

というわけで前回からパワーアップし、入力中に制限をかけられないか模索中です。

 

  1. 【PreviewKeyDownイベント】現在のテキスト内容とカーソル位置を保持
  2. 【KeyPressイベント】前回の「入力制限(数値だったりマイナスだったりの制限)」を記述
  3. 【ここ模索中】入力値を含めたテキスト内容で「正規表現での制御」を記述。一致しなかった場合、1で保持しておいた元のテキストとカーソルの位置を描画する。
  4. 【Leaveイベント】カンマ区切りかつ小数部2桁のフォーマットを記述。

なんてやり方で試しているのですが。。。。
手順3で「入力値を含めたテキスト内容」を取得する方法がどうしても見つからず(力技でならいけるのかもしれませんが、どうしてもシンプルにすませたい病のため)苦戦中;;
TextChangedは手順4が通ったときも発動してしまうし、そもそも初期値を設定しただけでも動いてしまうイベントなのでできれば使いたくない……(便利と不便は紙一重だなとしみじみ)

 

力技だと。。。手順3を手順2に吸収し、カーソルの位置に入力文字(e.KeyChar)を挿入するって感じでしょうか……。

続きは明日。

 

 

 

 

というわけで続きです。

手順3を手順2に吸収し、カーソルの位置に入力文字(e.KeyChar)を挿入する

調べてみたらこれが1行で実現できたので試してみました。
結局KeyPressイベントですべてを処理しきってしまうのがあれこれ考える必要なさそうだったのでまとめています。

<余談>
KeyPressイベント、KeyUpイベント、KeyDownイベント、PressKeyDownイベントなど色々ありますが、高速で入力すると2個目のPressKeyDownイベントがスキップされたり処理に必ずしも通るとは限らない状態が生まれてしまい、使い勝手はよくなかったです……。
これらのイベントに関してはいつかまとめたいなと思ってます。

 

話が若干それましたが以下コードです。

  1:        private void textBox2_KeyPress(object sender, KeyPressEventArgs e)
  2:        {
  3:
  4:            TextBox target = sender as TextBox;
  5:
  6:            // 入力時のカーソル位置
  7:            int inp_curpos = target.SelectionStart;
  8:            // 選択状態の文字数
  9:            int inp_sltLng = target.SelectionLength;
 10:            // 入力後の文字列形成
 11:            string aft_str = target.Text.Insert(inp_curpos, e.KeyChar.ToString()); 
 12:            if (inp_sltLng > 0) { aft_str = aft_str.Remove(inp_curpos + 1, inp_sltLng); } // 選択状態からの上書きであれば、上書きされた後の文字列を取得する
 13:            aft_str = aft_str.Replace(",", "");                                          // カンマが含まれている場合はカンマを取り除く
 14:            // 入力形式指定(正規表現
 15:            Regex r = new Regex(@"^-?\d{1,4}[.]\d{0,2}$|^-?\d{0,4}$");
 16:
 17:            // 制御文字は入力可
 18:            if (char.IsControl(e.KeyChar))
 19:            {
 20:                e.Handled = false;
 21:                label1.Text = "";
 22:                return;
 23:            }
 24:
 25:            // 「.」または「-」または数値1-9以外ははじいて処理終了
 26:            if (e.KeyChar == '.') 
 27:            {
 28:                if (((inp_sltLng != 0 && target.SelectedText.IndexOf('.') < 0)) && target.Text.IndexOf('.') > 0)  // 選択範囲に.がない or すでに.が使用されている
 29:                {
 30:                    e.Handled = true;
 31:                    return;
 32:                }
 33:            }else if (e.KeyChar == '-')
 34:            {
 35:                if (((inp_sltLng != 0 && target.SelectedText.IndexOf('-') < 0)) && target.Text.IndexOf('-') > 0) // 選択範囲に-がない or すでに-が使用されている
 36:                {
 37:                    e.Handled = true;
 38:                    return;
 39:                }
 40:            }else if (char.IsDigit(e.KeyChar) == false && e.KeyChar != '\b')
 41:            {
 42:                e.Handled = true;
 43:                return;
 44:            }
 45:
 46:            
 47:            // 入力形式判定
 48:            if (r.IsMatch(aft_str))
 49:            {
 50:                e.Handled = false;
 51:                label1.Text = "";
 52:            }
 53:            else
 54:            {
 55:                e.Handled = true;
 56:                label1.Text = "入力形式:整数1~4桁、小数点0~2桁";
 57:            }
 58:        }
 59:
 60:
 61:        private void textBox2_Leave(object sender, EventArgs e)
 62:        {
 63:            TextBox target = sender as TextBox;
 64:            //1文字目が「.」または「-」の場合、スルー
 65:            if(target.Text == "" || target.Text == "-." || target.Text.Length == 1 && 
 66:                                        (target.Text.Substring(0, 1) == "-" || target.Text.Substring(0, 1) == ".") || Math.Truncate(double.Parse(target.Text)).ToString().Length > 4) 
 67:            {
 68:                target.Text = "0";
 69:            }
 70:            target.Text = (double.Parse(target.Text)).ToString("N2");
 71:        }

 

前回からの違いは色々ありますが、大きく変更したのは以下3点

  • 入力中であることを考慮して正規表現を変更
  • 入力を受け付ける文字列の判定を変更(受け付けないパターンを判定条件としてreturnで抜ける)
  • 最期に正規表現判定

一方通行の入力なら問題ないのですが、入力して消して入力して消してという動作が取れる都合、「1234.56」と入力した後「-.56」という値に書き換えることもできてしまうわけです。
使う側にとっては不自然ではない動きでも、それがシステムエラーとなるようであれば入力を制限しなければなりません。
今回は入力への制限は極力なくし、フォーマットエラーの原因となる文字列を無理やり0にしています。めちゃくちゃ力技ですね^^

正直、backspaceを問答無用で受け付けてreturnしてしまってるせいで正規表現が2割型意味をなしていないんですよね……。そこをL65で無理やり矯正している感じです。

backspaceの考慮は色々と試しましたが、ただただコードが長くなるだけで解決方法が見えていないので一先ずこちらでフィニッシュとします;

 

2023/4/13 追記

L66について、考慮が足りないパターンがあったため変更しました。

 

 

backspaceの制限について

以下のように色々試したのですが。。。。

  1:        private void textBox2_KeyPress(object sender, KeyPressEventArgs e)
  2:        {
  3:
  4:            TextBox target = sender as TextBox;
  5:
  6:            // 入力時のカーソル位置
  7:            int inp_curpos = target.SelectionStart;
  8:            // 選択状態の文字数
  9:            int inp_sltLng = target.SelectionLength;
 10:            // テキストボックスの先頭で制御文字はスルー
 11:            if (char.IsControl(e.KeyChar) && inp_curpos == 0)
 12:            {
 13:                e.Handled = false;
 14:                label1.Text = "";
 15:                return;
 16:            }
 17:            // 入力後の文字列形成
 18:            string aft_str = "";
 19:            if (e.KeyChar == '\b')
 20:            {
 21:                if(inp_sltLng > 0)
 22:                {
 23:                    //選択範囲を削除
 24:                    aft_str = target.Text.Remove(inp_curpos, inp_sltLng);
 25:                }
 26:                else
 27:                {
 28:                    //カーソルの位置からひとつ前の文字を消す
 29:                    aft_str = target.Text.Remove(inp_curpos - 1, 1);
 30:                }
 31:            }
 32:            else
 33:            {
 34:                aft_str = target.Text.Insert(inp_curpos, e.KeyChar.ToString());
 35:                if (inp_sltLng > 0)
 36:                {
 37:                    // 選択状態からの上書きであれば、上書きされた後の文字列を取得する
 38:                    aft_str = aft_str.Remove(inp_curpos+1, inp_sltLng);
 39:                }             
 40:            }
 41:
 42:            aft_str = aft_str.Replace(",", "");                                          // カンマが含まれている場合はカンマを取り除く
 43:            // 入力形式指定(正規表現
 44:            Regex r = new Regex(@"^-?\d{1,4}[.]\d{0,2}$|^-?\d{0,4}$");
 45:
 46:            // 「.」または「-」または数値1-9以外ははじいて処理終了
 47:            if (e.KeyChar == '.') 
 48:            {
 49:                if (((inp_sltLng != 0 && target.SelectedText.IndexOf('.') < 0)) && target.Text.IndexOf('.') > 0)  // 選択範囲に.がない or すでに.が使用されている
 50:                {
 51:                    e.Handled = true;
 52:                    return;
 53:                }
 54:            }else if (e.KeyChar == '-')
 55:            {
 56:                if (((inp_sltLng != 0 && target.SelectedText.IndexOf('-') < 0)) && target.Text.IndexOf('-') > 0) // 選択範囲に-がない or すでに-が使用されている
 57:                {
 58:                    e.Handled = true;
 59:                    return;
 60:                }
 61:            }else if (char.IsDigit(e.KeyChar) == false && e.KeyChar != '\b')
 62:            {
 63:                e.Handled = true;
 64:                return;
 65:            }
 66:
 67:            
 68:            // 入力形式判定
 69:            if (r.IsMatch(aft_str))
 70:            {
 71:                e.Handled = false;
 72:                label1.Text = "";
 73:            }
 74:            else
 75:            {
 76:                e.Handled = true;
 77:                label1.Text = "入力形式:整数1~4桁、小数点0~2桁";
 78:            }
 79:        }

L11について、backspace入力時に先頭にカーソルがある場合(=テキストボックスが空の時も該当)処理をスルーをしないとL29でエラーになってしまうんですよね……。
しかし、例として1.23の整数部分を選択状態にしてbackspaceを押すと削除できてしまうんです。
あちらが立てばこちらが立たずってやつですね。
そもそもbackspace処理が行われた前提の内容をコードで書こうとしてるんだから無理がありますよね……。処理後の状態を取得できるいい関数があればいいのですが……そんなのあったイベント意味~~ってなるので諦めます;