WinForm UI验证

时间:2009-04-20 17:00:36

标签: c# .net winforms validation user-interface

我需要在整个winform应用程序中实现输入验证。有许多不同的形式可以输入数据,我希望不通过表单控制来控制每个项目创建isValid等。别人怎么处理这个?

我发现大多数相关帖子都涉及网络应用和/或提及Enterprise Library Validation Application Block。现在我承认我还没有彻底研究过ELVAB,但它似乎就像我需要的东西一样过分。我目前的想法是编写一个具有各种要求的类库,并将控件作为参数传递给它。我已经为 isValidZipCode 之类的东西提供了一个RegEx函数库,这样我就可以开始了。

我想要的是一个验证按钮,onClick循环遍历该表单页面上的所有控件并执行所需的验证。我怎么能做到这一点?

11 个答案:

答案 0 :(得分:62)

验证已内置到WinForms库中。

每个Control派生对象都有两个名为ValidatingValidated的事件。它还有一个名为CausesValidation的属性。当此设置为true(默认情况下为true)时,控件将参与验证。否则,它没有。

验证作为焦点的一部分发生。当您关注控件时,会触发其验证事件。事实上,焦点事件是按特定顺序触发的。来自MSDN

  

使用时更改焦点   键盘(TAB,SHIFT + TAB等),   通过调用选择或   SelectNextControl方法,或者   设置   一个ContainerControl .. ::。ACTIVECONTROL   财产到目前的形式,重点   事件按以下顺序发生:

     
      
  1. 输入
  2.   
  3. 的GotFocus
  4.   
  5. 保留
  6.   
  7. 验证
  8.   
  9. 验证
  10.   
  11. LostFocus
  12.         

    使用时更改焦点   鼠标或通过调用Focus方法,   焦点事件发生在以下   顺序:

         
        
    1. 输入
    2.   
    3. 的GotFocus
    4.   
    5. 引发LostFocus
    6.   
    7. 保留
    8.   
    9. 验证
    10.   
    11. 验证
    12.         

      如果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)

在我自己的应用程序中,我需要在输入时验证尺寸。我使用的顺序如下

  1. 用户选择或输入然后移动 远离控制。
  2. 控件失去焦点并通知 视图发送它的ID和 条目文本。
  3. View会检查Shape Program (实现接口的类) 创建了表单并将其传递给了 ID和输入文字
  4. Shape程序返回一个 响应。
  5. 如果响应正常,则查看 更新正确的形状输入 类。
  6. 如果响应正常,则视图会显示 表格通过它的界面 可以将焦点转移到下一个条目。
  7. 如果响应不正常,则查看 查看响应并使用 表单界面告诉表单是什么 去做。这通常意味着焦点 转回违规入场 显示消息告诉你 用户发生了什么。
  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>

事件:

  1. 将发件人转换为文本框。
  2. 检查文本框中的值是否为数字。
  3. 等等你排队了。

    希望这有帮助。

答案 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)

为什么不使用验证事件?您可以拥有一个验证事件并验证其中的控件。不需要使用循环,并且在输入数据时将验证每个控件。