Created
October 19, 2009 16:26
-
-
Save jpoehls/213496 to your computer and use it in GitHub Desktop.
WinForms validation based on IExtenderProvider
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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