Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

WPF Validation: How to Validate the Whole Form on the Button Click

DZone's Guide to

WPF Validation: How to Validate the Whole Form on the Button Click

In this tutorial, you'll learn how to add a validation field to your application that will be able to tell the user if they've entered incorrect credentials.

· Web Dev Zone
Free Resource

Learn how to build modern digital experience apps with Crafter CMS. Download this eBook now. Brought to you in partnership with Crafter Software

User data warrants verification in just about every application containing these forms. In all likelihood, if you have experience working with WPF application forms, you’ve had to deal with the implementation of validation in some capacity. In spite of the large amount of choices on hand, most of them are developed to function at a “field level.” As such, upon searching for “WPF validation,” you’ll learn how to use IDataErrorInfo. What if the entire form warrants validation, though?

WPF Application Example

For instance, you have an app that has authorization built into it, as well as an authorization window like this one:

Image title

We’ll get the following if the code is expressed:

<Window>
  <Grid>         
    <TextBlock Text="Email: "/>
    <TextBox x:Name="EmailTextBox"/>

    <TextBlock Text="Password: "/>
    <TextBox x:Name="PasswordTextBox"/>

    <Button x:Name="LoginButton">Log in</Button>
  </Grid>
</Window>

You’ll have the login service, of course:

Public class LogInService
  {
    public bool LogIn(string email, string password)
    {
        // In real life you will have some server communication here...but for now:
        return email.Equals("valid@email.com") && password.Equals("validPassword");
    }
  }

As suggested, when you perform a query searching for the answer, you may use IDataErrorInfo to disable the Login button (depending on the results of validation). This method will warrant an abundance of interaction with the server. This is why I’ve written this article: a login request can’t be submitted on all password fields or email alterations as it might block your account or IP, among other issues.

The Answer – INotifyDataErrorInfo

Thankfully, we have INotifyDataErrorInfo and IDataErrorInfo, both of which can resolve the problem once implemented, so your model can be viewed as follows:

public class MainWindowViewModel : INotifyDataErrorInfo
  {
    public string Email { get; set; } = "";
    public string Pass { get; set; } = "";

    public IEnumerable GetErrors(string propertyName)
    {
        if (string.IsNullOrEmpty(propertyName) || (!HasErrors))
            return null;
        return new List<string>() {"Invalid credentials."};
    }

    public bool HasErrors { get; set; } = false;

    public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;

    public bool CheckCredentials()
    {
        HasErrors = !new LogInService().LogIn(Email, Pass);
        if (HasErrors)
        {
            ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs("Email"));
            ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs("Pass"));
        }
        else
        {
            return true;
        }
        return false;
    }
  }

Errors are shown by HasErrors. It’s set to check for credentials once. An actual error is returned by GetErrors. Subscribers are informed about new errors by ErrorsChanged. Also, we supplemented the public helper approach, CheckCredentials, which can be called from the view using our LoginService. Check out this example:

private void LogInButton_Click(object sender, RoutedEventArgs e)
   {
       if (((MainWindowViewModel) DataContext).CheckCredentials())
       {
           Close();
       }   
   }

Bindings will need to be added with ValidatesOnNotifyDataErrors=True in XAML:

<TextBox x:Name="EmailTextBox" Grid.Row="0" Grid.Column="1" Margin="5"
             Text="{Binding Email, ValidatesOnNotifyDataErrors=True}"/>
...
    <TextBox x:Name="PasswordTextBox" Grid.Row="1" Grid.Column="1" Margin="5"
             Text="{Binding Pass, ValidatesOnNotifyDataErrors=True}"/>

Once the app is launched and you enter an incorrect password/email once while clicking the login button, this is what you’ll see:

Image title

Using ValidationRule for Field Level Validation

Now let’s talk about actual field level validation. Think about the modifications we made in the INotifyDataErrorInfo and begin with the ValidationRule method. To see if the email is legitimate, we will use a validation rule of our own. There’s no need to do things from scratch or have your regexps rewritten. I recommend using the following System.Net.Mail.MailAddress:

public override ValidationResult Validate(object value, CultureInfo cultureInfo)
    {
        try
        {
            new MailAddress(value.ToString());
        }
        catch (Exception)
        {
            return new ValidationResult(false, "Please enter a valid email.");
        }
        return new ValidationResult(true, null);
    }

Then we add it to XAML:

<TextBox x:Name="EmailTextBox" Grid.Row="0" Grid.Column="1" Margin="5">
        <TextBox.Text>
            <Binding Path="Email" UpdateSourceTrigger="PropertyChanged">
                <Binding.ValidationRules>
                    <local:EmailValidationRule ValidationStep="RawProposedValue"/>
                </Binding.ValidationRules>
            </Binding>
        </TextBox.Text>
    </TextBox>

If you have already begun developing your app and it resembles this, then you typed in an incorrect email address:

Image title

It changes as you are typing and will disappear if you enter the valid email.

A Common Example IDataErrorInfo

Most of the data found online on this topic are in reference to IDataErrorInfo. The first step is to implement it into the view mode:

public class MainWindowViewModel : IDataErrorInfo
  {
...

    public string this[string columnName]
    {
        get
        {
            switch (columnName)
            {
                case "Email":
                    if (!IsValidEmail(Email))
                        return "Please enter a valid email.";
                    break;
            }
            return string.Empty;
        }
    }

    public string Error { get; }

    private bool IsValidEmail(string email)
    {
        try
        {
            new MailAddress(email);
        }
        catch (Exception)
        {
            return false;
        }
        return true;
    }
  }

Our indexer inspects the email field when using just the helper approach, IsValidEmail. You must include binding in XAML with ValidatesOnDataErrors=True

And in XAML:

<TextBox x:Name="EmailTextBox" Grid.Row="0" Grid.Column="1" Margin="5"
             Text="{Binding Email, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}"/>

If you run it once it develops, you’ll have a similar outcome as the one you obtained with the earlier method.

Displaying a Validation Error Text

If you must display a specific message rather than the generic red line around your text box, you can do so by having the ErrorTemplate overridden in XAML, like so:

<Validation.ErrorTemplate>
            <ControlTemplate>
                <StackPanel>
                    <AdornedElementPlaceholder/>
                    <TextBlock Text="{Binding [0].ErrorContent}" Foreground="Red"/>
                </StackPanel>
            </ControlTemplate>
        </Validation.ErrorTemplate>

If you add some space, the form will look like this when you testing it:

Image title

In Closing

WPF provides numerous tools to validate forms. If you would like to leave your thoughts or provide recommendations, please do so in the following comment section.

Crafter is a modern CMS platform for building modern websites and content-rich digital experiences. Download this eBook now. Brought to you in partnership with Crafter Software.

Topics:
wpf ,web dev ,validation

Published at DZone with permission of Victor Osetskyi, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}