Skip to main content

C# - Binding Validation Rules In WPF [Beginner]


Bindings are a major part of WPF, and a big part of what makes it quick to create user interfaces in. They can be a little wordy and hard to debug sometimes, but overall they are extremely useful. Hopefully, after today's tutorial, you will find them even more useful - because today we are going to talk about building validation rules right into your bindings. Yup, you heard right - bindings in WPF have built in support for validation, and a number of the common controls have built in ways for displaying validation errors.

Today we are going to build a simple little WPF application that has a TextBox in which the user is supposed to enter an IP Address (a v4 address - I was lazy and didn't want to write a validator for ipv6 as well :P). Below you can see a screenshot of the the app in action:

A valid IPv4 address
An invalid IPv4 address

So the first thing that we need to do to add validation to a binding is to create a validation rule. To do this, you have to create a class that derives from ValidationRule and implements the abstract method Validate. There is actually only one validation rule built into the .NET framework, the ExceptionValidationRule, so in most cases you will probably be writing the rules yourself.

Below you can see the code for our rule, the IPv4ValidationRule:
using System;
using System.Globalization;
using System.Windows.Controls;

namespace SOTCBindingValidation
{
  public class IPv4ValidationRule : ValidationRule
  {
    public override ValidationResult Validate(object value, 
      CultureInfo cultureInfo)
    {
      var str = value as string;
      if (String.IsNullOrEmpty(str))
      {
        return new ValidationResult(false,
          "Please enter an IP Address.");
      }

      var parts = str.Split('.');
      if (parts.Length != 4)
      {
        return new ValidationResult(false,
          "IP Address should be four octets, seperated by decimals.");
      }

      foreach (var p in parts)
      {
        int intPart;
        if (!int.TryParse(p, NumberStyles.Integer,
          cultureInfo.NumberFormat, out intPart))
        {
          return new ValidationResult(false,
            "Each octet of an IP Address should be a number.");
        }

        if (intPart < 0 || intPart > 255)
        {
          return new ValidationResult(false,
            "Each octet of an IP Address should be between 0 and 255.");
        }
      }

      return new ValidationResult(true, null);
    }
  }
}
 
Before you comment on it, yes I know that you can check if a string is an IP Address by using the IPAddress.Parse method. For this example, though, using that wasn't nearly as interesting - plus, unlike this validation rule, it doesn't give very informative errors as to why the given string does not parse as an IP address.

Giving informative errors are one of the reasons to use validation rules - you get to send back information about the rule failure, instead of just a true/false. The Validate method always returns a ValidationResult, which is made up of two main parts - a boolean for if the rule passed/didn't pass, and an object holding extra information about the error. In our case here, that object is always an error string that we are later going to display to the user.

OK, now that we have a validation rule, how do we use it? Below you can see the C# and XAML code for the main window of our application:
using System.Windows;

namespace SOTCBindingValidation
{

  public partial class Window1 : Window
  {
    public static readonly DependencyProperty IPAddressProperty =
      DependencyProperty.Register("IPAddress", typeof(string),
      typeof(Window1), new UIPropertyMetadata("1.1.1.1"));

    public string IPAddress
    {
      get { return (string)GetValue(IPAddressProperty); }
      set { SetValue(IPAddressProperty, value); }
    }

    public Window1()
    { InitializeComponent(); }
  }
}
<Window x:Class="SOTCBindingValidation.Window1" x:Name="This"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:SOTCBindingValidation"
    Title="SOTC Validation Test" Height="150" Width="400">
  <Window.Resources>
    <local:ErrorsToMessageConverter x:Key="eToMConverter" />
  </Window.Resources>
  <StackPanel Margin="5">
    <TextBlock Margin="2">Enter An IPv4 Address:</TextBlock>
    <TextBox x:Name="AddressBox">
      <TextBox.Text>
        <Binding ElementName="This" Path="IPAddress" 
                 UpdateSourceTrigger="PropertyChanged">
          <Binding.ValidationRules>
            <local:IPv4ValidationRule />
          </Binding.ValidationRules>
        </Binding>
      </TextBox.Text>
    </TextBox>
    <TextBlock Margin="2" Foreground="Red" FontWeight="Bold" 
               Text="{Binding ElementName=AddressBox, 
                              Path=(Validation.Errors),
                              Converter={StaticResource eToMConverter}}" />
  </StackPanel>
</Window>
 
In terms of the C# code, there really isn't much to look at - we just have a dependency property for the IP address. It is the XAML that we are really interested in here. First thing you probably noticed is that we are declaring the binding for the TextBox using the full binding tags and syntax. This is because once you start adding things like validation rules, we can't use the markup extension anymore.

Inside the <Binding.ValidationRules> tags, we can list out all the rules that we want to run against this binding. In our case, we only have the one rule, but you can potentially have as many as you want. If you have more than one rule, all the rules get run (it doesn't short circuit after the first failure), so you get the accumulation of all the rule errors.

The rules get run every time the binding propagates changes from the target (in this case the TextBox) to the source (in this case the backing dependency property). Rules do not get run in the other direction - so if something invalid was pushed directly into the source, the target would show the value without running any rules. Because of this, validation rules only make sense if the BindingMode of the binding is OneWayToSource or TwoWay (the default).

By default, a TextBox binding only commits changes when it loses focus, but that is not nearly as interesting for this example. To get the validation rules to run as the user types in the TextBox, we set the UpdateSourceTrigger binding property to PropertyChanged. This means that the binding will be updated (and the rules will therefore be run) every time the target property is changed.

Looking at the XAML, you are probably wondering where the red outline comes from on the TextBox when there is an error. Well, it turns out that most of the built in WPF controls have a built in ErrorTemplate. This template defines what happens to the control in the case of a validation rule failure. I actually think the default error template is pretty good, but if you need to change it, you can treat it like any other control template, and set it in a style.

That about covers the TextBox, but now we need to take a look at how we are displaying the error message. Validation rule errors are held in an attached property called Validation.Errors. This is a collection of all the errors accumulated from all the rules (remember, we can have more than one), so we need a converter to convert that collection into a nice string to display:
using System;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Windows.Controls;
using System.Windows.Data;

namespace SOTCBindingValidation
{
  public class ErrorsToMessageConverter : IValueConverter
  {
    public object Convert(object value, Type targetType, object parameter, 
      System.Globalization.CultureInfo culture)
    {
      var sb = new StringBuilder();
      var errors = value as ReadOnlyCollection<ValidationError>;
      if (errors != null)
      {
        foreach(var e in errors.Where(e => e.ErrorContent != null))
        { sb.AppendLine(e.ErrorContent.ToString()); }
      }

      return sb.ToString();
    }

    public object ConvertBack(object value, Type targetType, object parameter, 
      System.Globalization.CultureInfo culture)
    { throw new NotImplementedException(); }
  }
}
 
A pretty simple converter - takes the error collection, and for each error that has an error message, append it to the accumulating string and then return the string. In the case were there are no errors, we will just get an empty string back.

The only trick to actually binding to the error collection is that it is an attached property, and so you need to use the attached property path syntax to reference it. That is why the Validation.Errors has parenthesis around it when it is set as the path of the binding. It is essentially saying to take the Errors attached property from the Validation class and find the value on the TextBox. Oh, the wonders and confusion of dependency properties!

Well, that is it for this introduction to adding validation rules to your bindings! You can grab a zip of the source code for the example we built here below.

Source Files:

Comments

Popular posts from this blog

C# Snippet - Shuffling a Dictionary [Beginner]

Randomizing something can be a daunting task, especially with all the algorithms out there. However, sometimes you just need to shuffle things up, in a simple, yet effective manner. Today we are going to take a quick look at an easy and simple way to randomize a dictionary, which is most likely something that you may be using in a complex application. The tricky thing about ordering dictionaries is that...well they are not ordered to begin with. Typically they are a chaotic collection of key/value pairs. There is no first element or last element, just elements. This is why it is a little tricky to randomize them. Before we get started, we need to build a quick dictionary. For this tutorial, we will be doing an extremely simple string/int dictionary, but rest assured the steps we take can be used for any kind of dictionary you can come up with, no matter what object types you use. Dictionary < String , int > origin = new Dictionary < string , int >();

C# Snippet - The Many Uses Of The Using Keyword [Beginner]

What is the first thing that pops into your mind when you think of the using keyword for C#? Probably those lines that always appear at the top of C# code files - the lines that import types from other namespaces into your code. But while that is the most common use of the using keyword, it is not the only one. Today we are going to take a look at the different uses of the using keyword and what they are useful for. The Using Directive There are two main categories of use for the using keyword - as a "Using Directive" and as a "Using Statement". The lines at the top of a C# file are directives, but that is not the only place they can go. They can also go inside of a namespace block, but they have to be before any other elements declared in the namespace (i.e., you can't add a using statement after a class declaration). Namespace Importing This is by far the most common use of the keyword - it is rare that you see a C# file that does not h

C# WPF Printing Part 2 - Pagination [Intermediate]

About two weeks ago, we had a tutorial here at SOTC on the basics of printing in WPF . It covered the standard stuff, like popping the print dialog, and what you needed to do to print visuals (both created in XAML and on the fly). But really, that's barely scratching the surface - any decent printing system in pretty much any application needs to be able to do a lot more than that. So today, we are going to take one more baby step forward into the world of printing - we are going to take a look at pagination. The main class that we will need to do pagination is the DocumentPaginator . I mentioned this class very briefly in the previous tutorial, but only in the context of the printing methods on PrintDialog , PrintVisual (which we focused on last time) and PrintDocument (which we will be focusing on today). This PrintDocument function takes a DocumentPaginator to print - and this is why we need to create one. Unfortunately, making a DocumentPaginator is not as easy as