WinForm UIvalidation
我需要在我的winform应用程序中实现inputvalidation。 有很多不同的数据可以input的forms,我不想通过控制forms去控制,并创buildisValid等每个项目。 别人怎么处理这个?
我发现大多数相关文章都涉及到Web Apps和/或提及企业库validation应用程序块 。 现在我承认我还没有彻底研究过ELVAB,但是对我所需要的东西来说似乎是过度的。 我目前的想法是编写一个具有各种要求的类库,并将其作为parameter passing给一个控件。 我已经有了像isValidZipCode这样的RegEx函数库,所以可能是我开始的地方。
我想要的是一个validationbutton,onClick循环通过该窗体页上的所有控件,并执行所需的validation。 我怎样才能做到这一点?
在我自己的应用程序中,我需要在input时validation尺寸。我使用的顺序如下所示
- 用户select或types然后离开控制。
- 控件失去焦点并通知View发送它的ID和input文本。
- 视图检查什么形状程序(实现接口的类)创build了表单并将其传递给标识和条目文本
- 形状程序返回一个响应。
- 如果响应正确,则视图更新形状类的正确input。
- 如果响应正常,则视图通过接口告诉表单,将焦点移到下一个条目是可以的。
- 如果响应不正确,View将查看响应,并使用Form Interface告诉表单执行的操作。 这通常意味着焦点转移回违规的项目,并显示一条消息告诉用户发生了什么事情。
这种方法的优点是validation集中在一个给定的形状程序的一个位置。 我不必去修改每个控件,甚至不用担心表单上不同types的控件。 当我devise软件的时候,我决定了UI如何用于文本框,列表框,combobox等。同样,不同级别的严重性处理方式也不同。
该视图负责指示表单通过界面执行什么操作。 它实际上是如何实现的,由窗体本身在接口的实现中处理。 如果表单显示为黄色警告,红色表示错误,则视图不关心。 只有它处理这两个层面。 后来,如果有更好的显示警告和错误的想法出现,我可以在窗体本身中进行更改,而不是使用视图逻辑或形状程序中的validation。
如果你正在考虑让一个类来保存你的validation逻辑,那么你已经到了一半了,这将会让你在新devise中继续前进。
validation已经内置到WinForms库中。
每个Control
引发的对象都有两个名为Validating
和Validated
事件。 它也有一个名为CausesValidation
的属性。 当它被设置为true(默认情况下是true),那么控件参与validation。 否则,它不会。
validation是作为焦点的一部分发生的。 当您关注某个控件时,其validation事件将被触发。 事实上,重点事件是按照特定的顺序进行的。 来自MSDN :
使用键盘(TAB,SHIFT + TAB等)更改焦点时,通过调用Select或SelectNextControl方法,或通过将ContainerControl .. ::。ActiveControl属性设置为当前窗体,焦点事件发生在以下顺序:
- input
- 的GotFocus
- 离开
- 证实
- validation
- 引发LostFocus
当您通过使用鼠标或通过调用焦点方法更改焦点时,焦点事件按以下顺序进行:
- input
- 的GotFocus
- 引发LostFocus
- 离开
- 证实
- validation
如果CausesValidation属性设置为false,则禁止validation和validation事件。
如果在Validating事件委托中将CancelEventArgs的Cancel属性设置为true,则通常在Validating事件之后发生的所有事件都将被禁止。
另外一个ContainerControl有一个名为ValidateChildren()
的方法,它将遍历包含的控件,并validation它们。
我意识到这个线程是相当古老的,但我想我会发布我提出的解决scheme。
在WinForms上进行validation的最大问题是validation仅在控件“失去焦点”时执行。 因此,用户必须在文本框内点击,然后点击其他地方才能执行validation例程。 如果您只关心input的数据是正确的,那么这很好。 但是,如果您试图确保用户没有跳过文本框,就不能正常工作。
在我的解决scheme中,当用户单击表单的提交button时,我检查表单上的每个控件(或任何指定的容器)并使用reflection来确定是否为控件定义了validation方法。 如果是,则执行validation方法。 如果任何validation失败,例程将返回一个失败,并允许进程停止。 这个解决scheme效果很好,特别是如果你有几个表单来validation。
1)只需将这部分代码复制并粘贴到您的项目中。 我们使用reflection,所以你需要添加System.Reflection到你的使用语句
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)在你想要validation的任何控件上使用标准的validation事件。 validation失败时务必使用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 。 即使在validation失败的情况下,这也将允许对其他控件进行切换。
4)最后,在您的提交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(); }
快乐编码!
我想不必通过控制来控制表格并创buildisValid等每个项目。
作为一个层次,你必须定义对每个控制valid
含义,除非你所关心的是控制具有某种价值。
也就是说,有一个你可以使用的ErrorProvider组件可以很好地工作。
我们用Noogen ValidationProvider祝你好运。 对于简单的情况(数据types检查和必填字段)很简单,并且可以方便地为更复杂的情况添加自定义validation。
在我的所有表单中,我为所讨论的特定控件实现了isValidating事件,如果数据没有validation,我在表单上有一个errorProvider,并使用它的SetError(…)方法将错误设置为控件有问题的相关资料,为什么它是错的。
编辑>我应该注意,我一般使用mvc模式时,这样做,所以模型的控制/成员的具体validation发生在模型,所以isValidating看起来有点像这样:
private uicontrol_isValidating(...) { if(!m_Model.MemberNameIsValid()) { errorProvider.SetError(...); } }
要么这样。 或者你可以有一个validation事件与所有需要类似validation的控件相关联。 这将从代码中删除循环。 假设你有四个只能有整数的文本框。 你可以做的是每个人都有一个单独的事件。 我没有任何IDE,所以下面的代码是最好的,我可以想出。
this.textbox1.Validated += <ValidatedEvent> this.textbox2.Validated += <ValidatedEvent> this.textbox3.Validated += <ValidatedEvent> this.textbox4.Validated += <ValidatedEvent>
在事件中:
- 发件人为文本框。
- 检查文本框中的值是否为数字。
等等,你有事件排队。
希望这可以帮助。
如果将上面的想法与这个通用的Validating事件处理程序结合起来,那么您的业务类中的所有validation方法都会得到一个很好的validation错误“框架”。 我只是用丹麦的想法扩展了Bruce的代码。 这是为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; } }
样品validation方法:
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."); } }
通过控制循环可以工作,但容易出错。 我曾经使用过这个技术(授予它是一个不是C#的Delphi项目)的项目,并且它按照预期工作,但是如果添加或更改了控件,更新非常困难。 这可能是可以纠正的。 我不确定。
无论如何,它通过创build一个单独的事件处理程序,然后附加到每个控件。 处理程序然后将使用RTTI来确定控件的types。 然后,它将在一个大的select语句中使用控件的name属性来查找要运行的validation代码。 如果validation失败,则向用户发送错误消息,并且控件被赋予焦点。 为了使事情变得更加复杂,表单被分成几个标签,并且为了获得焦点,它的子控件必须是可见的。
那是我的经验
我宁愿使用被动视图devise模式来从表单中删除所有业务规则,并将它们推送到Presenter类中。 取决于你的表格的状态,可能比你愿意投资更多的工作。
只是一个大概的想法:
void btnValidate_Click(object sender, EventArgs e) { foreach( Control c in this.Controls ) { if( c is TextBox ) { TextBox tbToValidate = (TextBox)c; Validate(tbToValidate.Text); } } }
void btnValidate_Click(object sender, EventArgs e) { foreach( Control c in this.Controls ) { if( c is TextBox ) { TextBox tbToValidate = (TextBox)c; Validate(tbToValidate.Text); } } }
如果你想避免循环其他控件,你可以将文本框粘贴在一个面板中,只能通过那里的控件循环。
为什么你不使用validation事件? 您可以有一个validation事件并validation那里的控件。 将不需要使用循环,每个控件将在input数据时进行validation。