24
votes

I'm having this really simple class, lets call it Customer. It look like this:

namespace TestValidation
{
     class Customer
     {
        private string _name;
        public string Name
        {
            get { return _name; }
            set
            {
                _name = value;
                if (String.IsNullOrEmpty(value))
                {
                    throw new Exception("Customer name is mandatory.");
                }
            }
        }
    }
}

Now, I've created a basic form, where the user can add customers to the database. The form contain simple TextBox, bounded to the Name property of Customer, and an "Add" button.

The XAML code is:

<Window x:Class="TestValidation.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:TestValidation"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
<TextBox Margin="119,86,107,194" Name="CustomerName"
        Text="{Binding Path=Customer.Name, 
                ValidatesOnExceptions=True, 
                ValidatesOnDataErrors=True,
                UpdateSourceTrigger=PropertyChanged,
                NotifyOnValidationError=True}"
    />
        <Button Content="Add" HorizontalAlignment="Left" Margin="204,176,0,0" VerticalAlignment="Top" Width="74"/>
    </Grid>
</Window> 

From the setter of the Name property, you can understand that the name is mandatory for me, so I want an validation event to rise if the Name TextBox left blank. By validation rules of WPF - once the user focus out of the textbox, and there's no value over there - it should change the border color to red. For some reason - this is not happening, and I don't have a clue why. What is wrong in my process?

Now, I've read so many good articles about Validation in WPF (like Enforcing Complex Business Data Rules with WPF, Data validation in WPF and Validation in Windows Presentation Foundation), but none of them helped me solving my problem.

Eventually, I want the form to look like the form in Brian Noyes excellent article over the first link (Don't have 10 credits, so I can't attach a photo... sorry).

I'll be grateful if someone can explain to me how it really works.

Important note - I'm working with .Net framework 4, so I need a solution that suits this version.

8

8 Answers

41
votes

I would definitely recommend using IDataErrorInfo for WPF validation since WPF already understands how to use it, and its easy to implement.

To start with, add the interface to the class containing the data you want to validate. The required methods will probably look something like this:

public class Customer : IDataErrorInfo
{
    ...

    #region IDataErrorInfo Members

    string IDataErrorInfo.Error
    {
        get { return null; }
    }

    string IDataErrorInfo.this[string columnName]
    {
        get
        {
            if (columnName == "Name")
            {
                // Validate property and return a string if there is an error
                if (string.IsNullOrEmpty(Name))
                    return "Name is Required";
            }

            // If there's no error, null gets returned
            return null;
        }
    }
    #endregion
}

Next, you need to set ValidatesOnDataErrors=True in your TextBox binding so it runs the validation whenever the Name property changes:

<TextBox Text="{Binding Path=Customer.Name, ValidatesOnDataErrors=True}" ... />

And finally, create a Validation Template in your XAML to tell WPF how to draw a validation error. Here's the style/template I usually use:

<!-- ValidatingControl Style -->
<Style TargetType="{x:Type FrameworkElement}" x:Key="ValidatingControl">
    <Style.Triggers>
        <Trigger Property="Validation.HasError" Value="True">
            <Setter Property="ToolTip" Value="{Binding 
                Path=(Validation.Errors)[0].ErrorContent, 
                RelativeSource={x:Static RelativeSource.Self}}" />
        </Trigger>
    </Style.Triggers>
</Style>

Also, be sure your Customer class implements INotifyPropertyChanged so it correctly responds to UI updates. I don't see that in your code, but often people leave that out for simplicity :)

2
votes

You did not specify a validation rule. The validation rule would be invoked before the control is left and then can do whatever you want to validate the inputs.

A simple example - and I guess that's what you want to do - is provided here.

2
votes

Use IDataErrorInfo for validation. this link will help you.

0
votes

I think the issue might be that your class isn't implementing INotifyPropertyChanged, so isn't binding as you're expecting.

Implement the INotifyPropertyChanged interface, raise an event when the property changed and it should work.

See http://msdn.microsoft.com/en-us/library/ms743695(v=vs.110).aspx for a walkthrough.

0
votes
<Binding Path=Name UpdateSourceTrigger="PropertyChanged">
  <Binding.ValidationRules>
    <ExceptionValidationRule />
  </Binding.ValidationRules>
</Binding>

http://msdn.microsoft.com/en-us/library/ms752347%28v=vs.110%29.aspx#what_is_data_binding

Please use this blog : prasadcsharp.blogspot.com

0
votes

Here is something that worked fine with me. No lag or long coding but I used it on double values only. You may change it as you need.

 private void search_box_TextChanged(object sender, TextChangedEventArgs e)
    {
        //  box text and background to normal state if user types numbers
        search_box.Foreground = Brushes.Black;
        search_box.Background = Brushes.White;

          if (search_id.IsSelected == true)
        {
            try
            {
                //convert while user is typing
                if (string.IsNullOrEmpty(search_box.Text)==false)
              Convert.ToDouble(search_box.Text);
                search_error.Text = null;
            }

            //if user types a letter or a space or a symbol  ====>
            catch (Exception)
            {
          //  user cant type any value other than numbers as exception prevents it and clears the box text value <======
                search_box.Text = null;
                search_box.Foreground = Brushes.White;
                search_box.Background = Brushes.Red;
                search_error.Text="id is numberic value";
            }
        }

        }

Hope it helps.

0
votes

1) when you use exceptions for validation, i reccomand to throw the exception before assigning the value to the property backing field, so you refuse it and your data-object (the Customer object in this case) will contain only valid data:

using System;

namespace TestValidation
{
    public class Customer
    {
        private string _name;
        public string Name
        {
            get => this._name;
            set
            {
                if (String.IsNullOrEmpty(value))
                    throw new ArgumentException("Customer name is mandatory.", nameof(Name));

                _name = value;
            }
        }
    }
}

2) By default, WPF data-binding engine ignores the exceptions that are raised in the setter procedure of the data objetc. You correctly set the ValidatesOnExceptions to true in order to instruct the data binding system to react on Exceptions. But, you set the UpdateSourceTrigger on PropertyChanged, thus the update of the property (Name) of the Source object (Customer) is triggered only when the Target property (Text) of the Target element (TextBox) is changed. If you start with an empty TextBox and just tab into it and than tab again away, the Text property has not been changed, so the updating of the Source property (Name) will no be triggered (this will happen even with LostFocus as the UpdateSourceTrigger mode). You can correct this just initializig the Text property to null or String.Empty in the costructor or in the Loaded event handler. This way, the textbox will appear with a red border as soon as the window is rendered. If you set UpdateSourceTrigger to LostFocus (that is the default for TextBox's Text property), the TextBox will appear initially without error, but if you tab in and out, it will be highlighted with the expected red border. Note: all this works because the Text property of the TextBox use TwoWay as the default binding mode, data can go from target to source.

using System.Windows;

namespace TestValidation
{
    public partial class MainWindow: System.Windows.Window
    {
        public CustomerTest()
        {
            InitializeComponent();
        }

        private void window_Loaded(object sender, RoutedEventArgs e)
        {
            this.txtCustomerName.Text = null;
        }
    }
}
<Window x:Class="TestValidation.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:ValidationTests"
        Title="MainWindow" Height="350" Width="525"
        Loaded="window_Loaded">

    <Window.Resources>
        <local:Customer x:Key="customer" />
    </Window.Resources>

    <Grid DataContext="{StaticResource customer}">
        <TextBox Margin="119,86,107,194"
                 x:Name="txtCustomerName" x:FieldModifier="protected"
                 Text="{Binding Path=Name, 
                                ValidatesOnExceptions=True,
                                UpdateSourceTrigger=PropertyChanged}" />
        <Button Content="Add" HorizontalAlignment="Center" Margin="204,176,0,0" VerticalAlignment="Top" Width="74"/>
    </Grid>
</Window>

3) In this case, the INotifyPropertyChanged is not required, since you are just interested in changing the value of the Source property (Name) by the interaction of the user in the TextBox, you are not modifying the Name property with other C# code. The INotifyPropertyChanged is implemented to notify the WPF data-binding system about changes in the data objetcs, so that WPF can update the data in user interface (update the Target when the Source is changed due to code procedures).

0
votes

You didn't implement INotifyPropertyChanged.
Also keep your attention to IDataErrorInfo and INotifyDataErrorInfo.. which are using in case if you want to move validation logic out of setters.
Also need admit that in modern app better move validation logic in separate type. (see fluentValidation)

using System;

namespace TestValidation
{
    public class Customer : INotifyPropertyChanged
    {
        private string _name;
        public string Name
        {
            get => this._name;
            set
            {
                if(_name == value) return;

                if (String.IsNullOrEmpty(value))
                    throw new ArgumentException("Customer name is mandatory.", nameof(Name));

                _name = value;

                OnPropertyChanged();
            }
        }

        #region INotifyPropertyChanged
        // TODO: Impelemnt interface INotifyPropertyChanged

        // Create the OnPropertyChanged method to raise the event
        // The calling member's name will be used as the parameter.
        protected void OnPropertyChanged([CallerMemberName] string name = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
        }
        #endregion
    }
}