C# ErrorProvider想知道是否有任何错误处于活动状态。

5

我想知道我的表格中是否有任何活动的ErrorProvider。

能够找到这一点可能有助于减少我的代码...

我在这里找到了一个东西 Counting ErrorProvider

但如果有人知道更好的方法...那么就来吧。

好的,基本上我有一个WinForm,其中有许多TextBoxes

现在,当用户输入值时,我使用Validating进行验证,如果它不符合Regex,则为该控件设置ErrorProvider。同样,如果用户将值更改为可接受的值,则我会关闭该控件的ErrorProvider。

但是,当单击保存时,我必须再进行另一个检查,以防用户没有听从我的建议,像他应该改变的那样并且仍然单击了保存。我不想让事情崩溃。

所以,是否有一种方法可以说如果ErrorProviders未启动,则继续保存,否则弹出消息框提示更改它。

[另一个问题]

当验证时,它只在控件失去焦点时进行验证...我希望它在用户停止输入时进行验证...我希望你明白我的意思

比如电子邮件地址(textbox),当用户输入他/她的名字时,我[不想]让它进行验证,但当用户完成输入等待ErrorProvider消失(但它没有,因为它只在控件失去焦点时才会出现)2秒钟后,可以让验证发生吗?

9个回答

15

很不幸,ErrorProvider控件并没有提供这样的功能。你最好使用链接中提供的自定义错误提供程序类。

否则,你可以创建一个方法来替代调用SetError

int errorCount;
void SetError(Control c, string message)
{
    if (message == "")
        errorCount--;
    else
        errorCount++;
    errorProvider.SetError(c, message);
}

或者你可以为ErrorProvider类创建一个扩展方法,该方法将设置错误并增加一个计数器或执行类似操作。

最后但并非不重要的是,您可以迭代所有控件。虽然速度较慢,但它能发挥作用:

bool IsValid()
{
    foreach (Control c in errorProvider1.ContainerControl.Controls)
        if (errorProvider1.GetError(c) != "")
            return false;
    return true;
}

编辑

我为错误提供程序编写了一个快速的扩展类:

public static class ErrorProviderExtensions
{
    private static int count;

    public static void SetErrorWithCount(this ErrorProvider ep, Control c, string message)
    {
        if (message == "")
        {
            if (ep.GetError(c) != "")
                count--;
        }
        else
            count++;

        ep.SetError(c, message);
    }

    public static bool HasErrors(this ErrorProvider ep)
    {
        return count != 0;
    }

    public static int GetErrorCount(this ErrorProvider ep)
    {
        return count;
    }
}

我还没有进行全面测试,因此在调用ErrorProviderSetError之前,您可能需要进行更多的验证。


非常感谢。我还没有测试过它...我会在几个小时后的考试之后测试它...但它看起来很不错...谢谢。 - RcK
刚试了一下,只是刚有时间,谢谢,我选择了这个扩展来完成它。 - RcK
1
似乎 count++ 部分只有在发生更改时才应该递增。即应该放在一个 if (ep.GetError(c) == "") 中。 - Brad
2
这不适用于多个或重复使用的ErrorProviders,因为它们共享静态计数字段。代码秘密依赖于用户修复所有错误后计数将被清零的事实! - Emond
我建议使用 Dictionary<Control, int> 来存储所有控件的错误计数,这样可以轻松检查是否仍有任何控件显示错误。 - royalTS

4

我知道这个问题有点老了,但是此扩展功能正常,除非有人尝试为相同的对象两次设置SetErrorWithCount,否则计数会被计算两次。 因此,我基于Netfangled扩展进行更新。

public static class ErrorProviderExtensions
{
   private static int count;

   public static void SetErrorWithCount(this ErrorProvider ep, Control c, string message)
   {
       if (message == "")
       {   
          if (ep.GetError(c) != "")
             count--;
       }
       else
          if (ep.GetError(c) == "")
             count++;

       ep.SetError(c, message);
   }

   public static bool HasErrors(this ErrorProvider ep)
   {
       return count != 0;
   }

   public static int GetErrorCount(this ErrorProvider ep)
   {
       return count;
   }
}

2

好的,让我使用更简单的方法:

目前您正在使用隐式验证方法...以立即验证控件。

我认为您想在执行某些操作之前检查表单中的所有控件是否已经验证通过,因此只需检查所有子控件是否已通过验证。通过使用显式验证方法

在每个控件的验证事件中,您可以使用以下内容:

    Private Sub ProductIDTextBox_Validating(sender As System.Object, e As System.ComponentModel.CancelEventArgs) Handles ProductIDTextBox.Validating
    If ProductIDTextBox.Text = "" Then
        ErrorProvider1.SetError(ProductIDTextBox, "you have to enter text")
        e.Cancel = True

        Return

    End If
    ErrorProvider1.SetError(ProductIDTextBox, "")

End Sub

然后您可以通过以下方式检查所有控件:

    Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click
    If ValidateChildren() Then
        MessageBox.Show("Validation succeeded!")
    Else
        MessageBox.Show("Validation failed.")
    End If
End Sub

希望这能帮到你,因为我花了好几个小时才找到适当的方法。


注意:要使ValidateChildren()返回false,必须在子控件的“Validating”事件中将e.Cancel设置为true。这会默认防止焦点离开无效控件。对于想要禁用此行为的用户,请将表单的AutoValidate属性设置为EnableAllowFocusChange - Eric Eskildsen

1
似乎这是一个合理的需求,但不幸的是它没有原生支持。
你可以像其他人提到的那样扩展ErrorProvider,或者简单地迭代其下所有控件并查找错误,类似于:
bool IsValidationError(ErrorProvider errorProvider, Control.ControlCollection controlCollection)
{
    foreach(Control child in controlCollection)
    {
        // The child or one of its children has an error.
        if (!errorProvider.GetError(child).IsNullOrEmpty() || IsValidationError(errorProvider, child.Controls))
            return true;
    }

    return false;
}

如果您想调用IsValidationError(errorProvider, errorProvider.ContainerControl.Controls),或者传递一个更有限的控件集合,那么您可以这样做。

显然,您会希望避免迭代大量控件,但是在许多情况下,这个简单的解决方案应该是可以的。即使您有很多控件,您也可能会使用PanelTabControlGroupBox等将它们分组,以便轻松避免迭代所有控件。

注意:这与https://dev59.com/KWjWa4cB1Zd3GeqPoDLu#12327212中描述的可能性类似,只不过它查找null和empty,并递归地迭代可能的子孙节点。


0

我的方法是使用扩展方法,这样我就可以简单地调用errorProvider.Valid()。如果正确实现,ErrorProvider实际上具有对其主控件(表单)的引用,因此它应该在所有具有单个实例的表单上工作。ValidateChildren()似乎没有返回有用的值。 这是我使用的:

public static bool Valid(this ErrorProvider ep)
{
  ep.ContainerControl.ValidateChildren();
  return ep.ChildrenAreValid(ep.ContainerControl);
}

private static bool ChildrenAreValid(this ErrorProvider ep, Control control)
{
  if (!string.IsNullOrWhiteSpace(ep.GetError(control))) return false;
  foreach (Control c in control.Controls)
    if (!(ep.ChildrenAreValid(c))) return false;
  return true;
}

通常我会有一个方法来启用/禁用保存按钮或类似的东西:
private bool VerifyIntegrity() => (btnSave.Enabled = errorProvider.Valid());

这是在输入事件时运行的。


0

这里一些答案极易出错,因为它们在扩展方法中共享一个静态计数变量。不行!

我的扩展方法使用我的Nuget包Overby.Extensions.Attachments来存储相关控件以及ErrorProvider,以便可以计算错误数量。

using System.Collections.Generic;
using System.Windows.Forms;
using System.Linq;
using Overby.Extensions.Attachments; // PM> Install-Package Overby.Extensions.Attachments

namespace MyApp
{
    public static class ErrorProviderExtensions
    {
        public static void TrackControl(this ErrorProvider ep, Control c)
        {
            var controls = ep.GetOrSetAttached(() => new HashSet<Control>()).Value;
            controls.Add(c);
        }

        public static void SetErrorWithTracking(this ErrorProvider ep, Control c, string error)
        {
            ep.TrackControl(c);          
            ep.SetError(c, error);
        }

        public static int GetErrorCount(this ErrorProvider ep)
        {
            var controls = ep.GetOrSetAttached(() => new HashSet<Control>()).Value;

            var errControls = from c in controls
                              let err = ep.GetError(c)
                              let hasErr = !string.IsNullOrEmpty(err)
                              where hasErr
                              select c;

            var errCount = errControls.Count();
            return errCount;
        }

        public static void ClearError(this ErrorProvider ep, Control c)
        {            
            ep.SetError(c, null);
        }
    }
}

0

我有多个控件元素(TextBox)与它们对应的ErrorProviders相连。

我试图找到一种方法来countAllErrors(),或者更好的是,handleEachError()
所以我想到了这个:


在课堂上:

internal TextBox email_textbox;
internal TextBox city_textbox;
internal TextBox address_textbox;
internal TextBox phone_textbox;
internal TextBox lastName_textbox;
internal TextBox firstName_textbox;
private ErrorProvider firstName_errPro;
private ErrorProvider lastName_errPro;
private ErrorProvider phone_errPro;
private ErrorProvider address_errPro;
private ErrorProvider city_errPro;
private ErrorProvider email_errPro;
internal Dictionary<ErrorProvider, Control> errors;

在表单的构造函数中:

errors = new Dictionary<ErrorProvider, Control>(6);
errors.Add( firstName_errPro ,firstName_textbox );
errors.Add( lastName_errPro  ,lastName_textbox  );
errors.Add( phone_errPro     ,phone_textbox     );
errors.Add( address_errPro   ,address_textbox   );
errors.Add( city_errPro      ,city_textbox      );
errors.Add( email_errPro     ,email_textbox     );

统计所有错误:

int countAllErrors()
{
    int numOfErrors = errors.Count<KeyValuePair<ErrorProvider, Control>>(ep => ep.Key.GetError(ep.Value) != "");
    return numOfErrors;
}

处理每个错误:

void handleEachError()
{

    foreach (KeyValuePair<ErrorProvider, Control> errPair in errors.Where(ep => ep.Key.GetError(ep.Value) != ""))
    {
        ErrorProvider   errorProvider   = errPair.Key;
        Control         control         = errPair.Value;
        string          errorStr        = errorProvider.GetError(control);

        // handle the error:
        // for example - show it's text in a MessageBox:
        MessageBox.Show(errorStr);
    }

}

如果有帮助的话,请告诉我.. ;)


0
在我的情况下,我没有使用静态类而是使用错误计数器的实例。
public class ErrorCounter
{
    private List<string> _propertiesError = new List<string>();
    private static ObjectIDGenerator _IDGenerator = new ObjectIDGenerator();

    public bool HasErrors
    {
        get => ErrorCount != 0;
    }

    public int ErrorCount
    {
        get => _propertiesError.Count;
    }

    /// <summary>
    /// Record object validation rule state.
    /// </summary>
    /// <param name="sender">"this" object reference must be passed into parameter each time SetError is called</param>
    /// <param name="message"></param>
    /// <param name="property"></param>
    /// <returns></returns>
    public string SetError(object sender, string property, string message)
    {
        string propertyUniqueID = GetPropertyUniqueID(sender, property);

        if (string.IsNullOrWhiteSpace(message))
        {
            if (_propertiesError.Exists(x => x == propertyUniqueID))
            {
                _propertiesError.Remove(propertyUniqueID);
            }
        }
        else
        {
            if (!_propertiesError.Exists(x => x == propertyUniqueID))
            {
                _propertiesError.Add(propertyUniqueID);
            }
        }

        return message;
    }

    private string GetPropertyUniqueID(object sender, string property)
    {
        bool dummyFirstTime;

        return property + "_" + _IDGenerator.GetId(sender, out dummyFirstTime);
    }
}

用法: 在你的主ViewModel中声明

public class MainViewModel : ViewModelBase, IDataErrorInfo
...
private ErrorCounter _errorCounter = new ErrorCounter();
...
// Entry validation rules
public string Error => string.Empty;
public string this[string columnName]
{
    get
    {
        switch (columnName)
        {
            case nameof(myProperty_1):
                if (string.IsNullOrWhiteSpace(myProperty_1))
                    return _errorCounter.SetError(this, columnName, "Error 1");
                break;
            case nameof(myProperty_2):
                if (string.IsNullOrWhiteSpace(myProperty_2))
                    return _errorCounter.SetError(this, columnName, "Error 2");
                break;
            default:
                break;
        }

        return _errorCounter.SetError(this, columnName, string.Empty);
    }
}

ObjectIDGenerator与属性名称组合使用,可以仅计算每个属性一次。 如果您需要在另一个类的对象集合成员中使用_errorCounter的同一实例,请将其传递给其他类的构造函数。

就是这些了:-)


0

你也可以简单地创建一个继承类。

public class TrackedErrorProvider : ErrorProvider
{
    public TrackedErrorProvider() : base() { }

    public TrackedErrorProvider(ContainerControl parentControl) : base(parentControl) { }

    public TrackedErrorProvider(IContainer container) : base(container) { }

    public int ErrorsCount { get; protected set; } = 0;

    public bool HasErrors
    {
        get { return ErrorsCount > 0; }
    }

    public new void SetError(Control control, string message)
    {
        //Check if there is already an error linked to the control
        bool errorExistsForControl = !string.IsNullOrEmpty(GetError(control));

        //If removing error from the control
        if (string.IsNullOrEmpty(message))
        {
            /* Decreases the counter only if:
            *   - an error already existed for the control
            *   - the counter is not 0
            */
            if (errorExistsForControl && ErrorsCount > 0) ErrorsCount--;
        }
        else //If setting error message to the control
        {
            //Increments the error counter only if an error wasn't set for the control (otherwise it is just replacing the error message)
            if (!errorExistsForControl) ErrorsCount++;
        }

        base.SetError(control, message);
    }

    public void RemoveError(Control control)
    {
        SetError(control, null);
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接