質問

I want to know if any ErrorProvider are active in my form. being able to find this out might help reduce my code..

I did find this thing here Counting ErrorProvider

but incase someone knows a better way... so here goes.

Ok so basically I have a WinForm which has many TextBoxes Now when user enters values I use Validating to perform validation and if it does not match Regex I set the ErrorProvider ON for that Control.. similarly if the user changes the value to a acceptable one I switch ErrorProvider OFF for that Control..

but when SAVE is clicked i have to do another check anyways incase the user did not listen to me and change the thing like he was supposed to and still clicked SAVE.. I dont want the thing crashing..

soo mm is there like a thing where I could say if ErrorProviders is not active then proceed with save else message box saying change it.

[ANOTHER QUESTION]

Umm When Validating it only Validates when the Control loses Focus... I kinda of want it to do validation when user stops typing.. I hope you get what I mean

Like Email Address(textbox) when user is typing his/her name in I [DON'T] want it to do validation yet, but when user has finished entering is waiting for ErrorProvider to disappear(But it doesn't coz it only does that when control loses focus) 2 odd seconds after typing can i make the validation take place?

役に立ちましたか?

解決

Unfortunately, the ErrorProvider control doesn't provide such functionality. You'd best go with the custom error provider classes from the link you posted.

Otherwise, you could create a method that you would call instead of SetError

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

Or you could make an extension method for the ErrorProvider class that would set the error and increment a counter or something along those lines.

And last but not least, you could iterate through all the controls. Slow, but it works:

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

Edit

I've written a quick extension class for the error provider:

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

I haven't tested it extensively, so you might want to do a bit more validation before calling SetError on your ErrorProvider.

他のヒント

I know this is a bit older question and the extension is working except if someone try to SetErrorWithCount twice for the same object, the count is counted twice. so, here I come with the update extension base on Netfangled extension

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

OK let me use easier method: currently you are using implicit validation approach... to immediately validate the control.

I think you want to check if all the controls in the form is validated before do some actions, so just check that all the child control is validated. by using The explicit validation approach

in the validating event for each control you can use:-

    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

then you can check for all the controls by :-

    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

hope this will help since i spend hours to find the proper method

It seems like a logical thing to have but unfortunately it's not provided natively.

You could extend the ErrorProvider as other mentioned, or simply iterate all the controls under it and look for an error, something like

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

and you'd call IsValidationError(errorProvider, errorProvider.ContainerControl.Controls), or passing a more limited control collection.

Obviously you'd want to avoid iterating tons of controls, but that simple solution should be fine in lots of cases. Also even if you do have tons of controls, you'd probably group them together using Panel, TabControl, GroupBox, ... so you could easily avoid iterating absolutely all the controls.

Note: this is similar to one of the possibilities described in https://stackoverflow.com/a/12327212/276648 except it looks for both null and empty, and it iterates possible grand children recursively.

I have multiple Control elements (TextBoxes) attached to their corresponding ErrorProviders.

I was trying to find a way to countAllErrors(), or even better, to handleEachError(),
so that's what I came up with:


In the Class:

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;

In the Form's Constructor:

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

Counting all errors:

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

Handling each error:

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

}

lemme know if it was helpful.. ;)

You can also simply create an inherited class.

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

Some answers here are extremely error prone, because they share a static count variable in the extension method. No!

My extension methods use my Nuget package Overby.Extensions.Attachments to store the associated controls along with the ErrorProvider so that the # of errors can be counted.

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

My way is with extension methods so I can simply call errorProvider.Valid(). The ErrorProvider actually has a reference to its main control(the form) if implemented correctly, so it should work on all forms with a single instance. ValidateChildren() doesn't seem to return a useful value. This is what I use:

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

Typically I have a method to enable/disable a save button or something as such:

private bool VerifyIntegrity() => (btnSave.Enabled = errorProvider.Valid());

Which is run at input events.

In my case, I didn't use a static class but an instance of error counter.

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

Usage : Declare in your main 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 combined with the propertie name allows to count only once each properties. If you need to use the same instance of _errorCounter in an object collection member of another class, pass it to the constructor of the other class.

and that's all :-)

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top