定制C#TextBox控件中只允许输入数字的解决方法(转载)

来源:互联网 发布:linux下安装jmeter 编辑:程序博客网 时间:2024/06/09 23:37

最近看到一些关于TextBox中限制只允许输入数字的博文,结合笔者前段时间修改完善的开源数值文框TNumEditBox控件,介绍一个解决方法。

     在定制的TextBox控件中,如果只允许输入数字,需要考虑如下三种情况:

  1. 正常按键输入的字符,包括西文、中文字符等
  2. 通过键盘快捷键方式贴入的文本,即Ctrl+V操作
  3. 通过上下文关联菜单的Mouse操作贴入的文本,即”粘贴“操作

     在探讨的同类文章中,多数只考虑了第1种情况,忽略得了第2、3种常见的操作。本文探讨的处理方法核心思路是重载事件OnKeyPress()和两个法ProcessCmdKey()与WndProc(),并把Ctrl+V、关联菜单的Paste操作统一到键盘录入操作中,从而在OnKeyPress()屏蔽掉非数字键。

1、重载键盘事件OnKeyPress()

     键盘输入的字符可以通过重载TextBox控件的OnKeyPress()事件处理,见如下代码:

     /// <summary>
    /// 屏蔽非数字键
    /// </summary>
    protected override void OnKeyPress(KeyPressEventArgs e)
    {
        base.OnKeyPress(e);

        if (this.ReadOnly)
        {
            return;
        }
        
        // 特殊键, 不处理
        if ((int)e.KeyChar <= 31)
        {
            return;
        }

        // 非数字键, 放弃该输入
        if (!char.IsDigit(e.KeyChar))
        {
            e.Handled = true;
            return;
        }
    }

2、重载命令键处理方法ProcessCmdKey()

      可以在ProcessCmdKey()中捕获快捷键Ctrl+V操作。首先要清除当前的选择文本,然后读取剪切板ClipBoard中的内容,最后通过模拟键盘输入的方式”输入“ClipBoard的内容。需要指出,在ProcessCmdKey()方法中不能使用静态方法SendKeys.Send(),但可以通过控件的WndProc()方法发送字符消息以达到模拟键盘录入的目的。见如下代码:

    /// <summary>
    /// 捕获Ctrl+V快捷键操作
    /// </summary>
    protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
    {
        if (keyData == (Keys)Shortcut.CtrlV)  // 快捷键 Ctrl+V 粘贴操作
        {
            this.ClearSelection();

            string text = Clipboard.GetText();
            for (int k = 0; k < text.Length; k++) // can not use SendKeys.Send
            {
                // 通过消息模拟键盘输入, SendKeys.Send()静态方法不行
                SendCharKey(text[k]);
            }
            return true;
        }
        return base.ProcessCmdKey(ref msg, keyData);

    }

    /// <summary>
    /// 通过消息模拟键盘录入
    /// </summary>
    private void SendCharKey(char c)
    {
        Message msg = new Message();

        msg.HWnd = this.Handle;
        msg.Msg = WM_CHAR;
        msg.WParam = (IntPtr)c;
        msg.LParam = IntPtr.Zero;

        base.WndProc(ref msg);
    }

3、重载消息处理方法WndProc()

     可以在定制TextBox控件中创建无内容的上下文菜单对象,从而屏蔽该菜单,方法是在定制控件的构造函数中增加如下代码:

    public class CustomTextBox: TextBox
    {
        this.ContextMenu = new ConTextMenu();  // 创建无内容菜单对象
    }

     由于上下文菜单的Paste操作对应Windows的WM_PASTE消息,于是可以在控件的WndProc()方法中捕获该消息,然后获得剪切板ClipBoard中的内容,最后通过SendKeys.Send()方法模拟键盘录入操作。需要注意,这里不能调用前面ProcessCmdKey()中模拟键盘输入函数SendCharKey()。见如下代码:

    /// <summary>
    /// 捕获Mouse的Paste消息
    /// </summary>
    protected override void WndProc(ref Message m)
    {
        if (m.Msg == WM_PASTE)  // 选择上下文菜单的"粘贴"操作
        {
            this.ClearSelection();
            SendKeys.Send(Clipboard.GetText());  // 模拟键盘输入
        }
        else
        {
            base.WndProc(ref m);
        }
    }

4、消除选择ClearSelection()、删除字符DeleteText()

     还必须分析前面代码中的函数。其中,函数ClearSelection()用以清除当前的选择文本,即清除this.SelectedText;函数DeleteText()则删除当前字符。注意其中的技巧,就是转换Delete键操作为BackSpace操作。此外,DeleteText()函数还需要确定当前的this.SelectionStart值。具体代码如下:

    /// <summary>
    /// 清除当前TextBox的选择
    /// </summary>
    private void ClearSelection()
    {
        if (this.SelectionLength == 0)
        {
            return;
        }

        int selLength = this.SelectedText.Length;
        this.SelectionStart += this.SelectedText.Length;  // 光标在选择之后
        this.SelectionLength = 0;

        for (int k = 1; k <= selLength; k++)
        {
            this.DeleteText(Keys.Back);
        }
    }

    /// <summary>
    /// 删除当前字符, 并计算SelectionStart值
    /// </summary>
    private void DeleteText(Keys key)
    {
        int selStart = this.SelectionStart;

        if (key == Keys.Delete)  // 转换Delete操作为BackSpace操作
        {
            selStart += 1;
            if (selStart > base.Text.Length)
            {
                return;
            }
        }

        if (selStart == 0 || selStart > base.Text.Length)  // 不需要删除
        {
            return;
        }

        if (selStart == 1 && base.Text.Length == 1)
        {
            base.Text = "";
            base.SelectionStart = 0;
        }
        else  // selStart > 0
        {
            base.Text = base.Text.Substring(0, selStart - 1) +
                base.Text.Substring(selStart, base.Text.Length - selStart);
            base.SelectionStart = selStart - 1;
        }
    }

5、结语

     上述内容是从笔者的开源数值型数据编辑控件TNumEditBox中修改删减而来的,该控件考虑的情况比只允许数字输入要复杂得多,感兴趣者可以参考并指正。需要指出,TNumEditBox的核心思路来自免费的Delphi控件PBNumEdit和开源的C#控件BANumEdit。作为回报(giveback),笔者也将TNumEditBox开源并发布到CodeProject。。 

下面是源码

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Text;
  4. using System.Windows.Forms;
  5. namespace WindowsApplication1
  6. {
  7.     public class CustomTextBox : TextBox
  8.     {
  9.         private const int WM_CHAR  = 0x0102;  // 字符消息
  10.         private const int WM_PASTE = 0x0302;  // 上下文菜单"粘贴"消息
  11.         public CustomTextBox() { }
  12.         /// <summary>
  13.         /// 捕获Mouse的Paste消息
  14.         /// </summary>
  15.         protected override void WndProc(ref Message m)
  16.         {
  17.             if (m.Msg == WM_PASTE)  // 选择上下文菜单的"粘贴"操作
  18.             {
  19.                 this.ClearSelection();
  20.                 SendKeys.Send(Clipboard.GetText());  // 模拟键盘输入
  21.             }
  22.             else
  23.             {
  24.                 base.WndProc(ref m);
  25.             }
  26.         }
  27.         /// <summary>
  28.         /// 捕获Ctrl+V快捷键操作
  29.         /// </summary>
  30.         protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
  31.         {
  32.             if (keyData == (Keys)Shortcut.CtrlV)  // 快捷键 Ctrl+V 粘贴操作
  33.             {
  34.                 this.ClearSelection();
  35.                 string text = Clipboard.GetText();
  36.                 for (int k = 0; k < text.Length; k++) // can not use SendKeys.Send
  37.                 {
  38.                     // 通过消息模拟键盘输入, SendKeys.Send()静态方法不行
  39.                     SendCharKey(text[k]);
  40.                 }
  41.                 return true;
  42.             }
  43.             return base.ProcessCmdKey(ref msg, keyData);
  44.         }
  45.         /// <summary>
  46.         /// 屏蔽非数字键
  47.         /// </summary>
  48.         protected override void OnKeyPress(KeyPressEventArgs e)
  49.         {
  50.             base.OnKeyPress(e);
  51.             if (this.ReadOnly)
  52.             {
  53.                 return;
  54.             }
  55.             
  56.             // 特殊键, 不处理
  57.             if ((int)e.KeyChar <= 31)
  58.             {
  59.                 return;
  60.             }
  61.             // 非数字键, 放弃该输入
  62.             if (!char.IsDigit(e.KeyChar))
  63.             {
  64.                 e.Handled = true;
  65.                 return;
  66.             }
  67.         }
  68.         /// <summary>
  69.         /// 通过消息模拟键盘录入
  70.         /// </summary>
  71.         private void SendCharKey(char c)
  72.         {
  73.             Message msg = new Message();
  74.             msg.HWnd = this.Handle;
  75.             msg.Msg = WM_CHAR;
  76.             msg.WParam = (IntPtr)c;
  77.             msg.LParam = IntPtr.Zero;
  78.             base.WndProc(ref msg);
  79.         }
  80.         /// <summary>
  81.         /// 清除当前TextBox的选择
  82.         /// </summary>
  83.         private void ClearSelection()
  84.         {
  85.             if (this.SelectionLength == 0)
  86.             {
  87.                 return;
  88.             }
  89.             int selLength = this.SelectedText.Length;
  90.             this.SelectionStart += this.SelectedText.Length;  // 光标在选择之后
  91.             this.SelectionLength = 0;
  92.             for (int k = 1; k <= selLength; k++)
  93.             {
  94.                 this.DeleteText(Keys.Back);
  95.             }
  96.         }
  97.         /// <summary>
  98.         /// 删除当前字符, 并计算SelectionStart值
  99.         /// </summary>
  100.         private void DeleteText(Keys key)
  101.         {
  102.             int selStart = this.SelectionStart;
  103.             if (key == Keys.Delete)  // 转换Delete操作为BackSpace操作
  104.             {
  105.                 selStart += 1;
  106.                 if (selStart > base.Text.Length)
  107.                 {
  108.                     return;
  109.                 }
  110.             }
  111.             if (selStart == 0 || selStart > base.Text.Length)  // 不需要删除
  112.             {
  113.                 return;
  114.             }
  115.             if (selStart == 1 && base.Text.Length == 1)
  116.             {
  117.                 base.Text = "";
  118.                 base.SelectionStart = 0;
  119.             }
  120.             else  // selStart > 0
  121.             {
  122.                 base.Text = base.Text.Substring(0, selStart - 1) + 
  123.                     base.Text.Substring(selStart, base.Text.Length - selStart);
  124.                 base.SelectionStart = selStart - 1;
  125.             }
  126.         }
  127.     }
  128. }