在软件开发中,用户输入验证是一个至关重要的环节。它不仅关系到数据的准确性和完整性,还直接影响到应用程序的稳定性和用户体验。本文将探讨用户输入验证的最佳实践,包括何时进行验证、如何处理不同数据类型,以及如何避免常见的输入错误。
实时验证用户输入,即在用户输入数据的同时进行验证,可以带来诸多优势。这种方式可以尽早发现并阻止无效数据的输入,从而减少后续处理中的复杂性和潜在错误。然而,如果在用户完成输入后进行验证,例如在失去焦点(LostFocus)事件中进行,开发者将面临一些困难的选择。例如,如果数据无效,是否应该将焦点返回到当前控件,这可能会导致用户“卡”在某个控件中;或者允许焦点移动,但用户必须返回到无效的控件以纠正问题。
为了在数据输入之前阻止无效数据的输入,可以考虑实现一个名为 NumEdit 的控件。这个控件可以轻松处理 .NET 基础的多种数值数据类型,如不同大小的浮点数和整数类型,以及货币(内部处理为 Decimal)。
首先,仅过滤数字并不能允许小数点或负号("-")字符。通过一些修改,可以允许负数和小数点,但代码处理重复的小数点和仅在首位的负号字符时会变得复杂。其次,更重要的是输入的数值。如果正在处理 .NET 32位整数值,那么一旦输入的数值达到最大值,就需要停止进一步输入,否则将面临溢出错误。
为此,选择了使用 Type.Parse() 方法来测试有效的输入。如果 Parse() 方法抛出异常,那么知道 KeyPress 字符是无效的,应该被取消。这种技术在将 TextBox 字符串转换为数值时可以阻止溢出错误。它还可以阻止重复的小数点和重复的负号("-")字符。
最后是粘贴操作。在 KeyPress 事件中使用 IsDigit() 会阻止 Ctrl+V 粘贴键,但用户可能喜欢使用 Ctrl+V 粘贴。如果从 VS.NET 中移除 Ctrl+V 粘贴,会有什么感觉?添加对 Ctrl+V 粘贴的处理将使控件成为一个完整的、生产就绪的组件。
这个控件的美妙之处在于,再也不需要考虑数值数据输入的问题。只需添加控件,设置 InputType 属性,就可以开始使用了。
IsValid() 方法是 NumEdit 控件的核心。该方法在开始时进行一些初步检查,处理初始的负号("-")字符、任何前导 "0" 字符和空字符串。方法的其余部分只是尝试将字符串解析为选定的数据类型,并在抛出任何异常时返回 false。如果 IsValid() 方法返回 false,KeyPress 事件将移除当前的按键输入。
private bool IsValid(string val) {
// this method validates the ENTIRE string
// not each character
bool ret = true;
if (val.Equals("") || val.Equals(String.Empty)) return ret;
if (user) {
// allow first char == '-'
if (val.Equals("-")) return ret;
if (Min < 0 && val.Equals("-")) return ret;
}
// parse into dataType, errors indicate invalid value
// NOTE: parsing also validates data type min/max
try {
switch (m_inpType) {
case NumEditType.Currency:
decimal dec = decimal.Parse(val);
int pos = val.IndexOf(".");
ret = val.Substring(pos).Length <= 3;
ret &= Min <= (double)dec && (double)dec <= Max;
break;
case NumEditType.Single:
float flt = float.Parse(val);
ret &= Min <= flt && flt <= Max;
break;
case NumEditType.Double:
double dbl = double.Parse(val);
ret &= Min <= dbl && dbl <= Max;
break;
case NumEditType.Decimal:
decimal dec2 = decimal.Parse(val);
ret &= Min <= (double)dec2 && (double)dec2 <= Max;
break;
case NumEditType.SmallInteger:
short s = short.Parse(val);
ret &= Min <= s && s <= Max;
break;
case NumEditType.Integer:
int i = int.Parse(val);
ret &= Min <= i && i <= Max;
break;
case NumEditType.LargeInteger:
long l = long.Parse(val);
ret &= Min <= l && l <= Max;
break;
default:
throw new ApplicationException();
}
} catch {
ret = false;
}
return ret;
}
在添加了 Ctrl+V 粘贴代码后,发现基础类 TextBox 有一个默认的上下文菜单,包括粘贴命令。这个命令绕过了精心编写的所有代码,允许非数值输入进入 NumEdit 控件。找不到拦截这个粘贴命令的方法。解决方案是简单地用一个空菜单替换 TextBox 上下文菜单,从而移除上下文菜单。如果有人有关于如何覆盖这个上下文菜单的想法,会很乐意听到。
在完成 NumEdit 控件后,想到一些可能的增强。