数値の入力のみ受け付けるテキストボックス コンポーネントを作成する - C#

数値の入力のみを受け付けるテキストボックスのコンポーネントを作成します。

概要

こちらの記事では数値の入力のみを受け付けるテキストボックスのコードを紹介しましたが、IMEからの入力やクリップボードからのペーストでは数値以外の文字を入力できます。この記事ではIMEやクリップボードからの入力も検証し数値以外の文字は入力できないテキストボックスのコンポーネントを紹介します。

コンポーネントのコード

Windowsフォームアプリケーションのプロジェクトに[コンポーネント クラス]を追加します。コンポーネントクラスに以下のコードを記述します。
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Text.RegularExpressions;
using System.Runtime.InteropServices;


namespace NumericTextBoxComponent
{
  public partial class NumericTextBox : TextBox
  {
    const int WM_PASTE = 0x0302;
    const int WM_IME_COMPOSITION = 0x010F;
    const int GCS_RESULTSTR = 0x0800;

    [DllImport("Imm32.dll", SetLastError = true)]
    public static extern IntPtr ImmGetContext(IntPtr hWnd);

    [DllImport("Imm32.dll")]
    private static extern int ImmGetCompositionStringW(IntPtr himc, int dwIndex, 
      StringBuilder lpBuf, int dwBufLen);

    [DllImport("imm32.dll")]
    public static extern int ImmSetCompositionStringW(IntPtr himc, int dwIndex, 
      IntPtr lpComp, int dw, int lpRead, int dw2);
    
    [DllImport("Imm32.dll")]
    public static extern bool ImmReleaseContext(IntPtr hWnd, IntPtr himc);

    public NumericTextBox()
    {
      InitializeComponent();
    }

    public NumericTextBox(IContainer container)
    {
      container.Add(this);

      InitializeComponent();
    }
    
    protected override void WndProc(ref Message message)
    {
      switch (message.Msg) {
        case WM_PASTE:
          IDataObject data = Clipboard.GetDataObject();
          if (data != null){
            if (data.GetDataPresent(DataFormats.Text)){
              string str = (string)(data.GetData(DataFormats.Text));
              Regex reg = new Regex(@"^[0-9,\r\n\t]+$");
              Match match = reg.Match(str);
              if (match.Success == true) {
                str = str.Replace("\r", "");
                str = str.Replace("\n", "");
                str = str.Replace("\t", "");
                str = str.Replace(",", "");
                Clipboard.SetData(DataFormats.Text, str);
              }
              else {
                message.Result = IntPtr.Zero;
                return;              
              }
            }
          }
          break;
        case WM_IME_COMPOSITION:
          int lparam = (int)message.LParam;
          if ((lparam & GCS_RESULTSTR) != 0) {
            IntPtr imc = ImmGetContext(this.Handle);
            int len = ImmGetCompositionStringW(imc, GCS_RESULTSTR, null, 0);
            StringBuilder str = new StringBuilder(len);
            ImmGetCompositionStringW(imc, GCS_RESULTSTR, str, str.Capacity);
            ImmReleaseContext(this.Handle, imc);

            Regex reg = new Regex(@"^[0-9]+$");
            Match match = reg.Match(str.ToString());
            if (match.Success == true) {
            }
            else {
              message.Result = IntPtr.Zero;
              return;                         
            }
          }
          break;
      }
      base.WndProc(ref message);
    }

    protected override void OnKeyDown(KeyEventArgs e)
    {
      if (Keys.D0 <= e.KeyCode && e.KeyCode <= Keys.D9) {
        e.Handled = false;
      }
      else if (Keys.NumPad0 <= e.KeyCode && e.KeyCode <= Keys.NumPad9) {
        e.Handled = false;
      }
      else if (e.KeyCode == Keys.Delete || e.KeyCode == Keys.Back) {
        e.Handled = false;
      }
      else if (e.KeyCode == Keys.Tab) {
        e.Handled = false;
      }
      else if (e.KeyCode == Keys.Left || e.KeyCode == Keys.Right
        || e.KeyCode == Keys.Up || e.KeyCode == Keys.Down) {
        e.Handled = false;
      }
      else {
        e.Handled = true;
        e.SuppressKeyPress = true;
      }
    }
  }
}

解説

キーボードからの入力制限

OnKeyDownメソッドをオーバーライドし、キーボードからの入力を制限します。詳細はこちらの記事を参照してください。

クリップボードからの貼り付け制限

クリップボードからの貼り付けを制限するにはWndProcメソッドをオーバーライドし、WM_PASTEウィンドウメッセージをキャプチャーします。WM_PASTEウィンドウメッセージを受信した際にクリップボードを確認し、数値以外の文字が含まれていれば、WM_PASTEをキャンセルします。
下記のコードでは、数値以外にも",","\r","\n","\t"を許容し、","や改行コードが含まれる場合はそれらを削除して数値のみにした値をクリップボードに再設定しています。("1,256,000"といった文字列の貼り付けにも対応しています。)
switch (message.Msg) {
  case WM_PASTE:
    IDataObject data = Clipboard.GetDataObject();
    if (data != null){
      if (data.GetDataPresent(DataFormats.Text)){
        string str = (string)(data.GetData(DataFormats.Text));
        Regex reg = new Regex(@"^[0-9,\r\n\t]+$");
        Match match = reg.Match(str);
        if (match.Success == true) {
          str = str.Replace("\r", "");
          str = str.Replace("\n", "");
          str = str.Replace("\t", "");
          str = str.Replace(",", "");
          Clipboard.SetData(DataFormats.Text, str);
        }
        else {
          message.Result = IntPtr.Zero;
          return;              
        }
      }
    }
    break;
}

IMEからの入力制限

IMEからの入力を制限する場合は、WndProcメソッドをオーバーライドし、"WM_IME_COMPOSITION"ウィンドウメッセージをキャプチャします。入力内容が確定された場合はLParamにGCS_RESULTSTRが含まれますのでそれを検出します。IMEから送られる文字列は、ImmGetCompositionStringW() Windows APIで取得できますのでIMEから入力される文字列を取得し、数値以外の文字が含まれていれば、"WM_IME_COMPOSITION"をキャンセルします。

switch (message.Msg) {
  case WM_IME_COMPOSITION:
    int lparam = (int)message.LParam;
    if ((lparam & GCS_RESULTSTR) != 0) {
      IntPtr imc = ImmGetContext(this.Handle);
      int len = ImmGetCompositionStringW(imc, GCS_RESULTSTR, null, 0);
      StringBuilder str = new StringBuilder(len);
      ImmGetCompositionStringW(imc, GCS_RESULTSTR, str, str.Capacity);
      ImmReleaseContext(this.Handle, imc);

      Regex reg = new Regex(@"^[0-9]+$");
      Match match = reg.Match(str.ToString());
      if (match.Success == true) {
      }
      else {
        message.Result = IntPtr.Zero;
        return;                         
      }
    }
    break;
  }
}

UI

下図のUIを作成します。先に作成した"NumericTextBox"コントロールをフォームに設置します。

実行結果

プロジェクトを実行します。下図のウィンドウが表示されます。


キーボードからの入力でも、クリップボードからの貼り付け、IMEからの入力でも数値以外の文字は受け付けない動作になります。


著者
iPentecのメインプログラマー
C#, ASP.NET の開発がメイン、少し前まではDelphiを愛用
最終更新日: 2022-01-08
作成日: 2012-06-19
iPentec all rights reserverd.