Reputation: 486
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.
Upvotes: 27
Views: 54207
Reputation: 748
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
}
}
Upvotes: 1
Reputation: 1
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).
Upvotes: 0
Reputation: 66
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.
Upvotes: 0
Reputation: 11
<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
Upvotes: 0
Reputation: 132678
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 :)
Upvotes: 49
Reputation: 355
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.
Upvotes: 0
Reputation: 56747
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.
Upvotes: 2