Skip to content

Instantly share code, notes, and snippets.

@jpoehls
Created October 19, 2009 16:26
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jpoehls/213496 to your computer and use it in GitHub Desktop.
Save jpoehls/213496 to your computer and use it in GitHub Desktop.
WinForms validation based on IExtenderProvider
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Text.RegularExpressions;
using System.Windows.Forms;
namespace Controls
{
[ProvideProperty("Required", typeof(Control))]
[ProvideProperty("InitialValue", typeof(Control))]
[ProvideProperty("Mode", typeof(Control))]
[ProvideProperty("Level", typeof(Control))]
[ProvideProperty("ErrorMessage", typeof(Control))]
public class SuperValidator : Component, IExtenderProvider
{
#region ValidationLevel enum
public enum ValidationLevel
{
Error,
Warning
}
#endregion
#region ValidationMode enum
public enum ValidationMode
{
None,
Decimal,
Double,
Int32,
Year
}
#endregion
private static Color ErrorColor;
private static Color WarningColor;
private readonly List<Control> _errorFields;
private readonly Dictionary<Control, FieldInfo> _fieldHash;
//private readonly BalloonToolTip _tooltip;
private readonly List<Control> _warningFields;
private readonly ErrorProvider v_message;
static SuperValidator()
{
ErrorColor = Color.FromArgb(255, 153, 153);
WarningColor = Color.FromArgb(255, 255, 153);
}
public SuperValidator()
{
_fieldHash = new Dictionary<Control, FieldInfo>();
v_message = new ErrorProvider
{
BlinkStyle = ErrorBlinkStyle.NeverBlink,
ContainerControl = null
};
//_tooltip = new BalloonToolTip
// {
// Initial = 100,
// AutoPop = 119,
// Icon = BalloonToolTip.BalloonIcons.Error,
// Title = "Validation Error"
// };
_errorFields = new List<Control>();
_warningFields = new List<Control>();
}
public bool HasErrors
{
get { return _errorFields.Count > 0; }
}
public bool HasWarnings
{
get { return _warningFields.Count > 0; }
}
#region IExtenderProvider Members
public bool CanExtend(object extendee)
{
if (extendee is TextBox ||
extendee is ComboBox ||
extendee is YesNoRadioButtons)
{
return true;
}
return false;
}
#endregion
private void HookUpValidatingEvent(Control c)
{
// first remove it incase its already hooked up
c.Validating -= Field_Validating;
// now add it
c.Validating += Field_Validating;
}
private FieldInfo GetFieldInfo(Control c)
{
FieldInfo info = _fieldHash.ContainsKey(c) ? _fieldHash[c] : new FieldInfo();
if (!_fieldHash.ContainsKey(c))
{
_fieldHash.Add(c, info);
HookUpValidatingEvent(c);
}
return info;
}
private static string GetValueFromField(Control c)
{
if (c is YesNoRadioButtons)
{
return ((YesNoRadioButtons)c).Value.ToString();
}
return c.Text;
}
private static bool ValidateRequiredField(string fieldValue, string initialValue)
{
if ((fieldValue == initialValue
|| (string.IsNullOrEmpty(fieldValue) && string.IsNullOrEmpty(initialValue))))
{
return false;
}
return true;
}
private static bool ValidateFieldByMode(string fieldvalue, FieldInfo info)
{
switch (info.Mode)
{
case ValidationMode.None:
return true;
case ValidationMode.Int32:
int i;
return int.TryParse(fieldvalue, out i);
case ValidationMode.Decimal:
decimal d;
return decimal.TryParse(fieldvalue, out d);
case ValidationMode.Double:
double dd;
return double.TryParse(fieldvalue, out dd);
case ValidationMode.Year:
return RegexLib.YearRegex.IsMatch(fieldvalue);
}
return false;
}
public void Field_Validating(object sender, CancelEventArgs e)
{
var c = sender as Control;
if (c == null)
return;
if (_errorFields.Contains(c))
{
_errorFields.Remove(c);
}
if (_warningFields.Contains(c))
{
_warningFields.Remove(c);
}
FieldInfo info = _fieldHash[c];
string fieldValue = GetValueFromField(c);
bool valid = true;
if (info.Required && !ValidateRequiredField(fieldValue, info.InitialValue))
{
valid = false;
}
if (!string.IsNullOrEmpty(fieldValue) && !ValidateFieldByMode(fieldValue, info))
{
valid = false;
}
if (valid)
{
// valid
c.BackColor = SystemColors.Window;
v_message.SetError(c, null);
//_tooltip.SetBalloonText((Control)sender, null);
}
else
{
switch (info.Level)
{
case ValidationLevel.Error:
_errorFields.Add(c);
c.BackColor = ErrorColor;
break;
case ValidationLevel.Warning:
_warningFields.Add(c);
c.BackColor = WarningColor;
break;
}
//_tooltip.SetBalloonText((Control)sender, info.ErrorMessage);
v_message.SetError(c, info.ErrorMessage);
}
}
public bool Validate(Form form)
{
return Validate(form, true);
}
/// <summary>
/// Forces the Validating event to be called on all
/// Enabled controls on the specified form.
///
/// Shows Yes/No warning box if some fields still
/// have warnings.
///
/// Returns True/False whether the form is valid.
/// </summary>
public bool Validate(Form form, bool promptToContinueWithErrors)
{
form.ValidateChildren(ValidationConstraints.Enabled);
bool isValid = true;
if (HasErrors)
{
if (promptToContinueWithErrors)
{
DialogResult r = MessageBox.Show("Required fields have not been completed.\r\n\r\nContinue anyway?",
form.Text, MessageBoxButtons.YesNo);
if (r == DialogResult.No)
{
isValid = false;
}
}
else
{
isValid = false;
}
}
else if (HasWarnings)
{
DialogResult r = MessageBox.Show("Some fields still have warnings.\r\n\r\nContinue anyway?",
form.Text, MessageBoxButtons.YesNo);
if (r == DialogResult.No)
{
isValid = false;
}
}
if (!isValid)
{
if (HasErrors)
{
MoveFocusToFirstErrorField();
}
else if (HasWarnings)
{
MoveFocusToFirstWarningField();
}
}
return isValid;
}
private void BringParentTabIntoFocus(Control c)
{
TabPage tabPage = null;
// find the TabPage containing this control
// by looping thru its parent heirarchy
Control c2 = c.Parent;
while (c2 != null)
{
if (c2 is TabPage)
{
tabPage = (TabPage)c2;
break;
}
c2 = c2.Parent;
}
// if we didn't find a parent TabPage then quit
if (tabPage == null)
{
return;
}
// set the TabPage as the SelectedTab
var tabPanel = (TabControl)tabPage.Parent;
tabPanel.SelectedTab = tabPage;
}
private void MoveFocusToFirstWarningField()
{
Control c = _warningFields[_warningFields.Count - 1];
BringParentTabIntoFocus(c);
c.Focus();
}
private void MoveFocusToFirstErrorField()
{
Control c = _errorFields[_errorFields.Count - 1];
BringParentTabIntoFocus(c);
c.Focus();
}
#region "Required" property
public bool GetRequired(Control parent)
{
FieldInfo info = GetFieldInfo(parent);
return info.Required;
}
public void SetRequired(Control parent, bool value)
{
FieldInfo info = GetFieldInfo(parent);
info.Required = value;
}
#endregion
#region "InitialValue" property
public string GetInitialValue(Control parent)
{
FieldInfo info = GetFieldInfo(parent);
return info.InitialValue;
}
public void SetInitialValue(Control parent, string value)
{
FieldInfo info = GetFieldInfo(parent);
info.InitialValue = value;
}
#endregion
#region "Level" property
public ValidationLevel GetLevel(Control parent)
{
FieldInfo info = GetFieldInfo(parent);
return info.Level;
}
public void SetLevel(Control parent, ValidationLevel value)
{
FieldInfo info = GetFieldInfo(parent);
info.Level = value;
}
#endregion
#region "Mode" property
public ValidationMode GetMode(Control parent)
{
FieldInfo info = GetFieldInfo(parent);
return info.Mode;
}
public void SetMode(Control parent, ValidationMode value)
{
FieldInfo info = GetFieldInfo(parent);
info.Mode = value;
//if ( value == ValidationMode.None ) {
// parent.CausesValidation = false;
//}
//parent.CausesValidation = true;
}
#endregion
#region "ErrorMessage" property
public string GetErrorMessage(Control parent)
{
FieldInfo info = GetFieldInfo(parent);
return info.ErrorMessage;
}
public void SetErrorMessage(Control parent, string value)
{
FieldInfo info = GetFieldInfo(parent);
info.ErrorMessage = value;
}
#endregion
#region Nested type: FieldInfo
private class FieldInfo
{
public bool Required { get; set; }
public string Regex { get; set; }
public ValidationLevel Level { get; set; }
public string InitialValue { get; set; }
public ValidationMode Mode { get; set; }
public string ErrorMessage { get; set; }
}
#endregion
#region Nested type: RegexLib
private static class RegexLib
{
private const string STR_EMAIL_RGX = @"^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$";
private const string STR_YEAR_RGX = @"^\d{4}$";
static RegexLib()
{
EmailRegex = new Regex(STR_EMAIL_RGX, RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase);
YearRegex = new Regex(STR_YEAR_RGX, RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase);
}
public static Regex EmailRegex { get; private set; }
public static Regex YearRegex { get; private set; }
}
#endregion
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment