Validation and user input handling

Validating and Validated events, ErrorProvider, masked and numeric input, and how to keep a dialog open when the data is wrong.

Validating and Validated

The Validating event fires when focus tries to leave a control whose CausesValidation is true. Setting e.Cancel = true blocks the move, which is exactly the behaviour you want for a field the user must fix.

var errors = new ErrorProvider { BlinkStyle = ErrorBlinkStyle.NeverBlink };

void EmailBox_Validating(object sender, CancelEventArgs e)
{
    var text = emailBox.Text.Trim();
    if (text.Length == 0)
    {
        errors.SetError(emailBox, "Email is required.");
        e.Cancel = true;                       // focus stays here
        return;
    }
    if (!text.Contains('@'))
    {
        errors.SetError(emailBox, "Enter a valid email address.");
        e.Cancel = true;
        return;
    }
    errors.SetError(emailBox, string.Empty);   // clear the icon
}

void EmailBox_Validated(object sender, EventArgs e) => _draft.Email = emailBox.Text.Trim();
  • Use Validating to reject and Validated to commit - keeping the two concerns separate avoids half-saved state.
  • ErrorProvider needs a container: pass this or the form of the control so the icon is positioned relative to it.
  • Cancel buttons must have CausesValidation = false, otherwise the user cannot escape a form with an invalid field.
  • Never call ValidateChildren() from inside a Validating handler - it recurses.

Choosing input controls

InputControlNotes
Free textTextBoxSet MaxLength; use CharacterCasing for codes
Fixed-format textMaskedTextBoxA mask is a UI constraint, not validation - still validate the parsed value
Whole numbersNumericUpDownSet Minimum, Maximum and DecimalPlaces; it clamps rather than rejects
MoneyNumericUpDowndecimal is the correct type; never store currency in a double
One of manyComboBoxDropDownStyle = DropDownList prevents free text
DateDateTimePickerSet ShowCheckBox when the value is optional
Yes or noCheckBoxUse CheckState.Indeterminate for a tri-state only when it is genuinely meaningful
// a mask is not validation: "00/00/0000" still accepts 99/99/9999
var mask = new MaskedTextBox("00/00/0000") { InsertKeyMode = InsertKeyMode.Overwrite };
mask.Validating += (s, e) =>
{
    if (!DateTime.TryParseExact(mask.Text, "dd/MM/yyyy",
            CultureInfo.InvariantCulture, DateTimeStyles.None, out _))
    {
        errors.SetError(mask, "Enter a real date.");
        e.Cancel = true;
    }
};

amount.Maximum = 1_000_000m;
amount.DecimalPlaces = 2;
amount.ThousandsSeparator = true;

Validating a whole form

private void OnSave(object sender, EventArgs e)
{
    var ok = ValidateChildren(ValidationConstraints.Enabled | ValidationConstraints.Visible);
    if (!ok)
    {
        DialogResult = DialogResult.None;   // keep the dialog open
        return;
    }
    Commit();
    DialogResult = DialogResult.OK;
}

// disable implicit validation while a background load populates the form
private void BeginLoad()
{
    AutoValidate = AutoValidate.Disable;
}
private void EndLoad()
{
    AutoValidate = AutoValidate.EnableAllowFocusChange;
}
⚠️
AutoValidate.EnablePreventFocusChange is the default and it will trap the user in a single control if a validation rule can never be satisfied - for example, a value that only becomes valid after a later field is filled. Prefer EnableAllowFocusChange plus a disabled Save button, and always give the user a way out.

FAQ

Validating on every keystroke?
Not for blocking rules - that fights the user mid-typing. Validate on leave for format, and use a positive confirmation such as a green tick for live feedback that never blocks input.
Where should business rules live?
In a plain class you can unit test, not in the event handler. The handler should call the rule and translate the failure into an ErrorProvider message - one rule, one place.

Dialogs, multiple forms and MDI DataGridView in depth

Last refreshed 2026-09-18.