C#-WinForm UI验证

我需要在整个Winform应用程序中实施输入验证。 有很多不同的形式可以输入数据,我不想按表单逐个控制,也不希望为每个项目创建isValid等。 别人如何处理呢?

我看到大多数相关文章都涉及Web Apps,并且/或者提到了Enterprise Library Validation Application Block。 现在,我承认我还没有对ELVAB进行彻底的研究,但是对于我所需要的东西似乎有些过高。 我目前的想法是编写具有各种要求的类库,并将控件作为参数传递给它。 我已经有一个RegEx函数库来处理isValidZipCode之类的东西,因此这可能是我开始的地方。

我想要的是一个Validate按钮,该按钮onClick循环浏览该表单页面上的所有控件并执行所需的验证。 我该怎么做?

11个解决方案
63 votes

验证已内置到WinForms库中。

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

验证是关注的一部分。 当您将焦点移到控件上时,将触发其验证事件。 实际上,焦点事件是按特定顺序触发的。 从MSDN:

当您使用 键盘(TAB,SHIFT + TAB等), 通过调用选择或 SelectNextControl方法,或通过 设置 ContainerControl .. ::。ActiveControl 当前形式的属性,重点 事件按以下顺序发生:

  1. 输入
  2. 重点
  3. 离开
  4. 证实
  5. 已验证
  6. 失落的焦点

当您使用 鼠标或通过调用Focus方法, 焦点事件发生在下面 订购:

  1. 输入
  2. 重点
  3. 失落的焦点
  4. 离开
  5. 证实
  6. 已验证

如果CausesValidation属性为 设置为false,验证和 验证事件被抑制。

如果取消属性为 CancelEventArgs在 验证事件委托,所有事件 通常会在 验证事件被抑制。

另外,ContainerControl具有称为ValidateChildren()的方法,该方法将循环遍历包含的控件并对其进行验证。

Matt Brunell answered 2020-07-26T04:18:41Z
43 votes

我意识到这个线程已经很老了,但我想我应该发布解决方案。

在WinForms上进行验证的最大问题是仅在控件“失去焦点”时才执行验证。 因此,用户必须实际上在文本框内单击,然后在其他位置单击以执行验证例程。 如果您只关心输入的数据正确,那么可以。 但是,如果您试图确保用户没有跳过文本框为空,则此方法将无法正常工作。

在我的解决方案中,当用户单击表单的“提交”按钮时,我检查表单上的每个控件(或指定的任何容器),并使用反射来确定是否为控件定义了验证方法。 如果是,则执行验证方法。 如果任何验证失败,则例程返回失败并允许该过程停止。 该解决方案效果很好,尤其是在您有多种形式需要验证的情况下。

1)只需将这段代码复制并粘贴到您的项目中。 我们正在使用反射,因此您需要在您的using语句中添加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)在要验证的任何控件上使用标准的Validating事件。 验证失败时,请务必使用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方法并指定要检查的容器。 它可以是整个表单,也可以是面板或小组之类的表单上的容器。

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();
    }

编码愉快!

Bruce answered 2020-07-26T04:19:33Z
9 votes

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

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

这种方法的优点是,对于给定的Shape Program,验证集中在一个位置。 我不必去修改每个控件,甚至不必担心表单上控件的不同类型。 早在设计软件时,我就决定了UI如何用于文本框,列表框,组合框等。不同级别的严重性也有不同的处理方式。

视图负责指示窗体通过界面执行的操作。 实际实现的方式由Form本身在Interface的实现中处理。 视图并不在乎表单是显示黄色表示警告,还是显示红色表示错误。 只有它可以处理这两个级别。 稍后,如果出现更好的显示警告与错误的想法,我可以在窗体本身中进行更改,而不必考虑使用View逻辑或Shape Program中的validate。

如果您正在考虑制作一个类来保存验证逻辑,那么您已经到了一半,这将为您提供新设计的其余方法。

RS Conley answered 2020-07-26T04:16:58Z
4 votes

我不想不必按窗体进行控制,也不必为每个项目创建isValid等。

从某种程度上来说,您将必须定义每个控件的2982980447497697618432的含义,除非您关心的只是控件具有某种值。

就是说,有一个ErrorProvider组件可以很好地使用。

Joel Coehoorn answered 2020-07-26T04:20:03Z
3 votes

我们已经有了Noogen ValidationProvider的好运。 对于简单的案例(数据类型检查和必填字段),它很简单;对于更复杂的案例,它很容易添加自定义验证。

Jamie Ide answered 2020-07-26T04:20:23Z
2 votes

在我所有的表格中,我为有问题的特定控件实现isValidating事件,如果数据未通过验证,则表格上有errorProvider,并使用其SetError(...)方法将错误设置为控件 询问有关错误原因的相关信息。

编辑>我应该注意,我通常在执行此操作时使用mvc模式,因此该控件/模型成员的特定验证在模型中进行,因此isValidating看起来像这样:

private uicontrol_isValidating(...)
{
    if(!m_Model.MemberNameIsValid())
    {
        errorProvider.SetError(...);
    }
}
Steven Evers answered 2020-07-26T04:20:49Z
2 votes

无论哪种方式。 或者,您可以将单个验证事件与所有需要类似验证的控件或控件相关联。 这将从代码中删除循环。 假设您有四个文本框,只能包含整数。 您可以做的是每个人都有一个活动。 我没有任何IDE,因此以下代码是我能提供的最好的代码。

this.textbox1.Validated += <ValidatedEvent>
this.textbox2.Validated += <ValidatedEvent>
this.textbox3.Validated += <ValidatedEvent>
this.textbox4.Validated += <ValidatedEvent>

如果发生以下情况:

  1. 将发件人投射为文本框。
  2. 检查文本框中的值是否为数字。

依此类推,您安排了一些活动。

希望这可以帮助。

danish answered 2020-07-26T04:21:30Z
2 votes

如果将上面的想法与此通用的Validating事件处理程序结合使用,则您的业务类中的所有验证方法都将收到良好的验证错误“框架”。 我只是用丹麦的想法扩展了布鲁斯代码。这是针对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.");
    }
}
Rey answered 2020-07-26T04:21:55Z
1 votes

遍历控件可以工作,但是容易出错。 我在一个使用该技术的项目上工作(允许它是Delphi项目而不是C#),它确实按预期工作,但是如果添加或更改控件,则很难进行更新。 这可能是可以纠正的。 我不确定。

无论如何,它都是通过创建单个事件处理程序来工作的,然后将其附加到每个控件。 然后,处理程序将使用RTTI来确定控件的类型。 然后,它将在大型选择语句中使用控件的name属性,以查找要运行的验证代码。 如果验证失败,则会向用户发送一条错误消息,并将控件作为焦点。 为了使事情变得更复杂,该表单分为几个选项卡,并且适当的选项卡必须可见,以便其子控件获得焦点。

这就是我的经验。

我宁愿使用Passive View设计模式从表单中删除所有业务规则,并将其推送到Presenter类中。 根据您表单的状态,可能要比您愿意投资的工作更多。

Kenneth Cochran answered 2020-07-26T04:22:29Z
1 votes

只是一个大概的想法:


您可以将文本框粘贴在面板中,并仅在其中循环浏览控件(如果要避免循环浏览其他控件)。

Will Eddins answered 2020-07-26T04:22:54Z
1 votes

为什么不使用Validating事件? 您可以有一个验证事件并在那里验证控件。 无需使用循环,并且在输入数据时将验证每个控件。

danish answered 2020-07-26T04:23:14Z
translate from https://stackoverflow.com:/questions/769184/winform-ui-validation