Form validation in Windows Forms with C#

Tamás Polgár
Developer rants
Published in
6 min readApr 5, 2024

Looks like we are turning into a C# blog. I promise I will continue the AI series later.

Form validation is a somewhat complicated business on every platform, as it’s the first line of defense against the worst thing ever happened to software: users. Windows Forms offers a fairly straightforward way to ensure those meatbags won’t yank the controls like the stupid apes they are.

Step 1: Tell your project about your intents
To enable form validations, right click on your project in Solution Explorer, and select Add… > Reference.

The Reference Manager apparars. Find and check System.ComponentModel.DataAnnotations, then click OK.

Step 2: Define the form as a class
Right click your project again, and now select Add… > Class. Name your new class, and define each form field as a public value. For example, here is form data for a simple user registration form.

using System.ComponentModel.DataAnnotations;

namespace MyApplication
{

public class FormData_User
{
[Required]
[MinLength(1)]
public string Name { get; set; } = "";

[Required]
public bool Active { get; set; } = false;

[Required]
[Range(1, 120)]
public decimal Age { get; set; } = 30;

public string Remark { get; set; } = "";
}
}

By using System.ComponentModel.DataAnnotations, we can now add simple constraints to each value, using modifiers. This class is a highly versatile data validation library, and it’s certainly beyond the scope of this article to explain every bit of it. However, let’s delve on a few details for a moment.

For example, the [Required] modifier is a reference to the RequiredAttribute object. In the above example, there are no arguments for either use, but you can actually add some:

[Required(
AllowEmptyStrings = true
)]
public string Remark { get; set; }

Most validators accept arguments. For further details, refer to the documentation.

Step 3: Build your application
This is necessary for the next step.

Step 4: Create a data source
Go to your form, where the validation will take place. Click the Data sources tab on the left edge of the screen, and then click the Add new data source button.

The Data Source Configuration Wizard appears. Under the Where will the application get data from? question, click Object, then click Next >.

On the next list, open your project, and find the object you just created. Check the checkbox next to its name, and then click Finish.

Step 5: Create form controls
The data source appears on the sidebar. Open it, and you’ll see the fields you just defined.

Clicking a dropdown next to a field’s name reveals a list of UI controls that can be used to input this value: TextBox, ComboBox and others. If you aren’t satisfied with the options, click Customize… and you can configure which controls should be offered for various data types. You may need it because, for example, Visual Studio doesn’t think you may need a NumericUpDown for numeric values.

After selecting which field should use what UI component, just grab and drag each one to your form designer. Visual Studio will automatically generate the appropriate form element, complete with labels. They will be configured to the constraints you set (i.e. the range of numeric values).

If you wish to use existing form elements, you can pull a data source field over them to bind them to one. For example, grab Name in the list, drag it into the Designer, and drop it on a text field or combo box. It doesn’t matter if the control already has a DataSource property, the form binding won’t override it.

Step 6: Adding form buttons
A typical form always has a Submit button, and optionally, a Reset button. Let’s bring our form in some shape with these.

First of all, our form needs to receive its default values. This is done in a single line, preferably in the form’s Activated event:

private void Form1_Activated(object sender, EventArgs e)
{
formData_UserBindingSource.DataSource = new FormData_User();
}

This is also how you reset your form. You can bind the Reset button’s click event to this method, if nothing else is going to happen here.

But wait a minute, where did this formData_UserBindingSource come from?

It’s the name of the instance of our data source in Form1. Go to Form Designer, and you’ll see it on the bottom of the window. That’s where elements with no visual representation always appear.

The Submit button is a bit more complicated, because this is where validation will take place.

Step 7: Let’s validate!
Validation is a couple more lines long.

using System.ComponentModel.DataAnnotations;

...

private void button2_Click(object sender, EventArgs e)
{
formData_UserBindingSource.EndEdit();
FormData_User user = formData_UserBindingSource.Current as FormData_User;

ValidationContext context = new ValidationContext(user, null, null);
IList<ValidationResult> errors = new List<ValidationResult>();

if (!Validator.TryValidateObject(user, context, errors, true))
{
foreach (ValidationResult result in errors) {
MessageBox.Show(result.ErrorMessage, "Validation error", MessageBoxButtons.OK, MessageBoxIcon.Warning);
}
}
}

Wow, what’s happening here?

First, we prepare the validation mechanism with the EndEdit() method. This causes every pending change to be applied to the underlying data source (exact words from the documentation).

Second, we create a variable, user, to retrieve the current content of the form. Then we create context as a validation context, and errors to retrieve potential validation errors.

Third, we try to validate the object with TryValidateObject(). As a result, the errors list gets filled up (if there are any errors), and we show a MessageBox with each error. While this may not be the best way to show them, it’s perfect for a simple demonstration.

Congratulations, your validation now works! Barely, though.

Step 8: Let’s take it up a (tiny) notch!
You might want to use your own error messages. You might want different error messages for different errors. Let’s go back to our FormData_User class, and add some. For example, the Name field may have several validators. Let’s add custom error messages to each!

[Required(ErrorMessage = "Name must be submitted")]
[MinLength(1, ErrorMessage = "Name must not be empty")]
[MaxLength(40, ErrorMessage = "Name is too long")]
[RegularExpression(@"^[a-zA-Z]*", ErrorMessage = "The name may only contain letters")]
public string Name { get; set; } = "";

You might want to enable or disable the Submit button depending on whether there are any errors. This can be done by looking at errors.Count:

button1.Enabled = errors.Count == 0;

Naturally, if you want this, you need to run the validation every time any of the form controls change.

--

--