我需要在整个winform应用程序中实现输入验证。有许多不同的形式可以输入数据,我希望不通过表单控制来控制每个项目创建isValid等。别人怎么处理这个?
我发现大多数相关帖子都涉及网络应用和/或提及Enterprise Library Validation Application Block。现在我承认我还没有彻底研究过ELVAB,但它似乎就像我需要的东西一样过分。我目前的想法是编写一个具有各种要求的类库,并将控件作为参数传递给它。我已经为 isValidZipCode 之类的东西提供了一个RegEx函数库,这样我就可以开始了。
我想要的是一个验证按钮,onClick循环遍历该表单页面上的所有控件并执行所需的验证。我怎么能做到这一点?
答案 0 :(得分:62)
验证已内置到WinForms库中。
每个Control
派生对象都有两个名为Validating
和Validated
的事件。它还有一个名为CausesValidation
的属性。当此设置为true(默认情况下为true)时,控件将参与验证。否则,它没有。
验证作为焦点的一部分发生。当您关注控件时,会触发其验证事件。事实上,焦点事件是按特定顺序触发的。来自MSDN:
使用时更改焦点 键盘(TAB,SHIFT + TAB等), 通过调用选择或 SelectNextControl方法,或者 设置 一个ContainerControl .. ::。ACTIVECONTROL 财产到目前的形式,重点 事件按以下顺序发生:
- 输入
- 的GotFocus
- 保留
- 验证
- 验证
- LostFocus
醇>使用时更改焦点 鼠标或通过调用Focus方法, 焦点事件发生在以下 顺序:
- 输入
- 的GotFocus
- 引发LostFocus
- 保留
- 验证
- 验证
醇>如果CausesValidation属性是 设置为false,验证和 验证事件被抑制。
如果取消属性 CancelEventArgs在。中设置为true 验证事件委托,所有事件 通常会在之后发生 验证事件被禁止。
ContainerControl还有一个名为ValidateChildren()
的方法,它将遍历包含的控件并验证它们。
答案 1 :(得分:42)
我意识到这个线程很老了,但我想我会发布我提出的解决方案。
WinForms验证的最大问题是验证仅在控件“失去焦点”时执行。因此,用户必须实际单击文本框内部,然后单击其他位置以执行验证例程。如果您只关心输入的数据是正确的,那么这很好。但是,如果您尝试确保用户没有通过跳过文本框而将文本框留空,那么这不会很好。
在我的解决方案中,当用户单击表单的提交按钮时,我检查表单上的每个控件(或指定的任何容器)并使用反射来确定是否为控件定义了验证方法。如果是,则执行验证方法。如果任何验证失败,则例程返回失败并允许该过程停止。如果您有多种表单需要验证,此解决方案效果很好。
1)只需将此部分代码复制并粘贴到您的项目中即可。我们正在使用Reflection,因此您需要将System.Reflection添加到using语句
class Validation
{
public static bool hasValidationErrors(System.Windows.Forms.Control.ControlCollection controls)
{
bool hasError = false;
// Now we need to loop through the controls and deterime if any of them have errors
foreach (Control control in controls)
{
// check the control and see what it returns
bool validControl = IsValid(control);
// If it's not valid then set the flag and keep going. We want to get through all
// the validators so they will display on the screen if errorProviders were used.
if (!validControl)
hasError = true;
// If its a container control then it may have children that need to be checked
if (control.HasChildren)
{
if (hasValidationErrors(control.Controls))
hasError = true;
}
}
return hasError;
}
// Here, let's determine if the control has a validating method attached to it
// and if it does, let's execute it and return the result
private static bool IsValid(object eventSource)
{
string name = "EventValidating";
Type targetType = eventSource.GetType();
do
{
FieldInfo[] fields = targetType.GetFields(
BindingFlags.Static |
BindingFlags.Instance |
BindingFlags.NonPublic);
foreach (FieldInfo field in fields)
{
if (field.Name == name)
{
EventHandlerList eventHandlers = ((EventHandlerList)(eventSource.GetType().GetProperty("Events",
(BindingFlags.FlattenHierarchy |
(BindingFlags.NonPublic | BindingFlags.Instance))).GetValue(eventSource, null)));
Delegate d = eventHandlers[field.GetValue(eventSource)];
if ((!(d == null)))
{
Delegate[] subscribers = d.GetInvocationList();
// ok we found the validation event, let's get the event method and call it
foreach (Delegate d1 in subscribers)
{
// create the parameters
object sender = eventSource;
CancelEventArgs eventArgs = new CancelEventArgs();
eventArgs.Cancel = false;
object[] parameters = new object[2];
parameters[0] = sender;
parameters[1] = eventArgs;
// call the method
d1.DynamicInvoke(parameters);
// if the validation failed we need to return that failure
if (eventArgs.Cancel)
return false;
else
return true;
}
}
}
}
targetType = targetType.BaseType;
} while (targetType != null);
return true;
}
}
2)对要验证的任何控件使用标准验证事件。 当验证失败时,务必使用e.Cancel!
private void txtLastName_Validating(object sender, CancelEventArgs e)
{
if (txtLastName.Text.Trim() == String.Empty)
{
errorProvider1.SetError(txtLastName, "Last Name is Required");
e.Cancel = true;
}
else
errorProvider1.SetError(txtLastName, "");
}
3)不要跳过此步骤!将表单上的 AutoValidate 属性设置为 EnableAllowFocusChange 。即使验证失败,这也允许标签到另一个控件。
4)最后,在Submit Button方法中,调用Validation方法并指定要检查的容器。它可以是整个表单,也可以只是表单上的容器,如Panel或Group。
private void btnSubmit_Click(object sender, EventArgs e)
{
// the controls collection can be the whole form or just a panel or group
if (Validation.hasValidationErrors(frmMain.Controls))
return;
// if we get here the validation passed
this.close();
}
快乐的编码!
答案 2 :(得分:8)
在我自己的应用程序中,我需要在输入时验证尺寸。我使用的顺序如下
这种方法的优点是验证集中在给定形状程序的一个位置。我不必去修改每个控件,甚至不用担心窗体上不同类型的控件。当我设计软件时,我决定了UI如何用于文本框,列表框,组合框等。另外,不同级别的严重性处理方式也不同。
View负责指导表单通过界面执行的操作。它实际如何实现是由Form本身在它的Interface实现中处理的。视图不关心表单是否显示黄色表示警告,红色表示错误。只有它处理这两个级别。稍后如果出现更好的显示警告与错误的想法,我可以在表单中进行更改,而不是使用View逻辑或Shape程序中的验证。
如果你正在考虑建立一个课程来保持你的验证逻辑,你已经在那里了一半,这将使你在新设计中得到余下的部分。
答案 3 :(得分:4)
我希望不必通过表单控制来控制每个项目创建isValid等。
在某种程度上,您必须为每个控件定义valid
的含义,除非您关心的是控件具有某种值。
那就是说,你可以使用的ErrorProvider component效果非常好。
答案 4 :(得分:3)
我们对Noogen ValidationProvider祝你好运。它对于简单的案例(数据类型检查和必填字段)很简单,并且可以为更复杂的案例添加自定义验证。
答案 5 :(得分:2)
在我的所有表单中,我为所讨论的特定控件实现了isValidating事件,如果数据没有验证,我在表单上有一个errorProvider,我使用它的SetError(...)方法来设置错误对相关控制的相关信息,说明为什么是错误的。
编辑>我应该注意到我在执行此操作时通常使用mvc模式,因此该模型的控件/成员的特定验证发生在模型中,因此isValidating看起来有点像这样:
private uicontrol_isValidating(...)
{
if(!m_Model.MemberNameIsValid())
{
errorProvider.SetError(...);
}
}
答案 6 :(得分:2)
要么那样。或者,您可以将一个验证事件与需要类似验证的所有或控件相关联。这将从代码中删除循环。假设您有四个文本框,只能包含整数。你可以做的是每个人都有一个单一的事件。我没有任何IDE,因此下面的代码是我能想到的最好的代码。
this.textbox1.Validated += <ValidatedEvent>
this.textbox2.Validated += <ValidatedEvent>
this.textbox3.Validated += <ValidatedEvent>
this.textbox4.Validated += <ValidatedEvent>
事件:
等等你排队了。
希望这有帮助。
答案 7 :(得分:2)
如果您将上述想法与此通用验证事件处理程序结合使用,您将获得一个良好的验证错误“框架”,其中包含业务类中的所有验证方法。我只是用丹麦的想法扩展了布鲁斯的代码。 这是为Entity Framework和Dev Express组件完成的,但可以轻松删除这些依赖项。 享受!
public class ValidationManager
{
/// <summary>
/// Call this method to validate all controls of the given control list
/// Validating event will be called on each one
/// </summary>
/// <param name="controls"></param>
/// <returns></returns>
public static bool HasValidationErrors(System.Windows.Forms.Control.ControlCollection controls)
{
bool hasError = false;
// Now we need to loop through the controls and deterime if any of them have errors
foreach (Control control in controls)
{
// check the control and see what it returns
bool validControl = IsValid(control);
// If it's not valid then set the flag and keep going. We want to get through all
// the validators so they will display on the screen if errorProviders were used.
if (!validControl)
hasError = true;
// If its a container control then it may have children that need to be checked
if (control.HasChildren)
{
if (HasValidationErrors(control.Controls))
hasError = true;
}
}
return hasError;
}
/// <summary>
/// Attach all youe Validating events to this event handler (if the controls requieres validation)
/// A method with name Validate + PropertyName will be searched on the binded business entity, and if found called
/// Throw an exception with the desired message if a validation error is detected in your method logic
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
public static void ValidationHandler(object sender, CancelEventArgs e)
{
BaseEdit control = sender as BaseEdit;
if (control.DataBindings.Count > 0) //control is binded
{
string bindedFieldName = control.DataBindings[0].BindingMemberInfo.BindingField;
object bindedObject = control.BindingManager.Current;
if (bindedObject != null) //control is binded to an object instance
{
//find and call method with name = Validate + PropertyName
MethodInfo validationMethod = (from method in bindedObject.GetType().GetMethods()
where method.IsPublic &&
method.Name == String.Format("Validate{0}",bindedFieldName) &&
method.GetParameters().Count() == 0
select method).FirstOrDefault();
if (validationMethod != null) //has validation method
{
try
{
validationMethod.Invoke(bindedObject, null);
control.ErrorText = String.Empty; //property value is valid
}
catch (Exception exp)
{
control.ErrorText = exp.InnerException.Message;
e.Cancel = true;
}
}
}
}
}
// Here, let's determine if the control has a validating method attached to it
// and if it does, let's execute it and return the result
private static bool IsValid(object eventSource)
{
string name = "EventValidating";
Type targetType = eventSource.GetType();
do
{
FieldInfo[] fields = targetType.GetFields(
BindingFlags.Static |
BindingFlags.Instance |
BindingFlags.NonPublic);
foreach (FieldInfo field in fields)
{
if (field.Name == name)
{
EventHandlerList eventHandlers = ((EventHandlerList)(eventSource.GetType().GetProperty("Events",
(BindingFlags.FlattenHierarchy |
(BindingFlags.NonPublic | BindingFlags.Instance))).GetValue(eventSource, null)));
Delegate d = eventHandlers[field.GetValue(eventSource)];
if ((!(d == null)))
{
Delegate[] subscribers = d.GetInvocationList();
// ok we found the validation event, let's get the event method and call it
foreach (Delegate d1 in subscribers)
{
// create the parameters
object sender = eventSource;
CancelEventArgs eventArgs = new CancelEventArgs();
eventArgs.Cancel = false;
object[] parameters = new object[2];
parameters[0] = sender;
parameters[1] = eventArgs;
// call the method
d1.DynamicInvoke(parameters);
// if the validation failed we need to return that failure
if (eventArgs.Cancel)
return false;
else
return true;
}
}
}
}
targetType = targetType.BaseType;
} while (targetType != null);
return true;
}
}
样品验证方法:
partial class ClientName
{
public void ValidateFirstName()
{
if (String.IsNullOrWhiteSpace(this.FirstName))
throw new Exception("First Name is required.");
}
public void ValidateLastName()
{
if (String.IsNullOrWhiteSpace(this.LastName))
throw new Exception("Last Name is required.");
}
}
答案 8 :(得分:1)
循环控制可以工作,但它容易出错。我参与了一个使用该技术的项目(授予它的是Delphi项目而不是C#)并且它确实按预期工作,但是如果添加或更改了控件则很难更新。这可能是可纠正的。我不确定。
无论如何,它通过创建一个事件处理程序然后附加到每个控件来工作。然后,处理程序将使用RTTI来确定控件的类型。然后它将在大型select语句中使用控件的name属性来查找要运行的验证代码。如果验证失败,则会向用户发送错误消息,并且控件将获得焦点。为了使事情变得更复杂,表单被分成几个选项卡,并且必须显示正确的选项卡,以便它的子控件能够获得焦点。
这就是我的经历。
我更愿意使用Passive View设计模式从表单中删除所有业务规则并将它们推送到Presenter类中。取决于您的表单状态可能比您愿意投资的工作更多。
答案 9 :(得分:1)
只是一个粗略的想法:
void btnValidate_Click(object sender, EventArgs e)
{
foreach( Control c in this.Controls )
{
if( c is TextBox )
{
TextBox tbToValidate = (TextBox)c;
Validate(tbToValidate.Text);
}
}
}
如果你想避免在其他控件中循环,你可以将文本框粘贴在面板中,只在那里循环控制。
答案 10 :(得分:1)
为什么不使用验证事件?您可以拥有一个验证事件并验证其中的控件。不需要使用循环,并且在输入数据时将验证每个控件。