在定制的TextBox控件中,如果只允许输入数字,需要考虑如下三种情况:
- 正常按键输入的字符,包括西文、中文字符等
- 通过键盘快捷键方式贴入的文本,即Ctrl+V操作
- 通过上下文关联菜单的Mouse操作贴入的文本,即”粘贴“操作
在探讨的同类文章中,多数只考虑了第1种情况,忽略得了第2、3种常见的操作。本文探讨的处理方法核心思路是重载事件OnKeyPress()和两个法ProcessCmdKey()与WndProc(),并把Ctrl+V、关联菜单的Paste操作统一到键盘录入操作中,从而在OnKeyPress()屏蔽掉非数字键。
1、重载键盘事件OnKeyPress()
键盘输入的字符可以通过重载TextBox控件的OnKeyPress()事件处理,见如下代码:
/// <summary>
///屏蔽非数字键
///</summary>
protectedoverride 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>
protectedoverride 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 useSendKeys.Send
{
// 通过消息模拟键盘输入, SendKeys.Send()静态方法不行
SendCharKey(text[k]);
}
return true;
}
return base.ProcessCmdKey(ref msg, keyData);
}
///<summary>
///通过消息模拟键盘录入
///</summary>
private voidSendCharKey(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控件中创建无内容的上下文菜单对象,从而屏蔽该菜单,方法是在定制控件的构造函数中增加如下代码:
publicclass CustomTextBox: TextBox
{
this.ContextMenu = newConTextMenu(); // 创建无内容菜单对象
}
由于上下文菜单的Paste操作对应Windows的WM_PASTE消息,于是可以在控件的WndProc()方法中捕获该消息,然后获得剪切板ClipBoard中的内容,最后通过SendKeys.Send()方法模拟键盘录入操作。需要注意,这里不能调用前面ProcessCmdKey()中模拟键盘输入函数SendCharKey()。见如下代码:
///<summary>
///捕获Mouse的Paste消息
///</summary>
protectedoverride 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 voidClearSelection()
{
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 voidDeleteText(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。。
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
namespace WindowsApplication1
{
public classCustomTextBox : TextBox
{
private const int WM_CHAR = 0x0102; // 字符消息
private const int WM_PASTE = 0x0302; // 上下文菜单"粘贴"消息
public CustomTextBox() { }
/// <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);
}
}
/// <summary>
/// 捕获Ctrl+V快捷键操作
/// </summary>
protected override bool ProcessCmdKey(ref Message msg, KeyskeyData)
{
if (keyData == (Keys)Shortcut.CtrlV) // 快捷键 Ctrl+V 粘贴操作
{
this.ClearSelection();
string text = Clipboard.GetText();
for (int k = 0; k < text.Length; k++) // can not useSendKeys.Send
{
// 通过消息模拟键盘输入, SendKeys.Send()静态方法不行
SendCharKey(text[k]);
}
return true;
}
return base.ProcessCmdKey(ref msg, keyData);
}
/// <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;
}
}
/// <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);
}
/// <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;
}
}
}
}