I've a class named Person
with two properties FirstName
, LastName,
two Constructors
, one ICommand
and usual stuffs required for INotifyPropertyChanged
and IDataErrorInfo
:
class Person : ObservableCollection<Person>, INotifyPropertyChanged, IDataErrorInfo
{
string firstName, lastName;
#region Properties
[Required(ErrorMessage = "First Name is Required")]
[RegularExpression("test", ErrorMessage = "It's to be test")]
public string FirstName {
get => firstName;
set { firstName = value; OnPropertyChanged(); }
}
[Required]
[RegularExpression("test", ErrorMessage = "It also has to be test")]
public string LastName {
get => lastName;
set { lastName = value; OnPropertyChanged(); }
}
#endregion Properties
#region Constructors
public Person(){
AddToList = new Command(CanAdd, Add);
}
public Person(string fName, string lName){
FirstName = fName;
LastName = lName;
}
#endregion Constructors
#region Command
public ICommand AddToList { get; set; }
bool CanAdd(object para) => Validator.TryValidateObject(this, new ValidationContext(this), null, true);
void Add(object para){
Add(new Person(FirstName, LastName));
FirstName = LastName = null;
}
#endregion Command
#region INotifyPropertyChanged
public new event PropertyChangedEventHandler PropertyChanged;
void OnPropertyChanged([CallerMemberName] string name = "") => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
#endregion INotifyPropertyChanged
#region IDataErrorInfo
public string Error => null;
public string this[string columnName] {
get {
var ValidationResults = new List<ValidationResult>();
if (Validator.TryValidateProperty(
GetType().GetProperty(columnName).GetValue(this),
new ValidationContext(this) { MemberName = columnName },
ValidationResults
)) return null;
return ValidationResults.First().ErrorMessage;
}
}
#endregion IDataErrorInfo
}
in xaml
I've two TextBox
bound to FirstName
and LastName
of Person, two Label
for validation error message and a Button
, bound to the ICommand, to add Person
in the following ListView
:
<Window ...>
<Window.Resources>
<local:Person x:Key="Person"/>
</Window.Resources>
<Grid DataContext="{StaticResource Person}">
<StackPanel>
<TextBox x:Name="Fname" Text="{Binding FirstName, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" />
<Label Content="{Binding (Validation.Errors)[0].ErrorContent, ElementName=Fname}"/>
<TextBox x:Name="Lname" Text="{Binding LastName, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}"/>
<Label Content="{Binding (Validation.Errors).CurrentItem.ErrorContent, ElementName=Lname}"/>
<Button Content="Click" Command="{Binding AddToList}" />
<ListView x:Name="lv" ItemsSource="{Binding}">
<ListView.View>
<GridView>
<GridViewColumn Header="First Name" Width="200"
DisplayMemberBinding="{Binding FirstName}"/>
<GridViewColumn Header="Last Name" Width="200"
DisplayMemberBinding="{Binding LastName}" />
</GridView>
</ListView.View>
</ListView>
</StackPanel>
</Grid>
</Window>
It works, I get error message if First and Last names are not valid and the Button remains disabled as long as any of the First and Last names is invalid. I want to replace only the IDataErrorInfo
part with INotifyDataErrorInfo
. What changes do I have to make in Person
class and xaml
to keep the same functionality?
The framework calls GetErrors
each time you raise the ErrorsChanged
event. Since there is an HasErrors
property that should return true
whenever there are any validation errors, it makes sense to validate in the property setters and cache the validation errors in a Dictionary<string, List<ValidationResult>>
.
Please refer to the following sample implementation:
class Person : ObservableCollection<Person>, INotifyPropertyChanged, INotifyDataErrorInfo
{
string firstName, lastName;
#region Properties
[Required(ErrorMessage = "First Name is Required")]
[RegularExpression("test", ErrorMessage = "It's to be test")]
public string FirstName
{
get => firstName;
set { firstName = value; OnPropertyChanged(); Validate(); }
}
[Required]
[RegularExpression("test", ErrorMessage = "It also has to be test")]
public string LastName
{
get => lastName;
set { lastName = value; OnPropertyChanged(); Validate(); }
}
#endregion Properties
#region Constructors
public Person()
{
AddToList = new Command(CanAdd, Add);
Validate(nameof(FirstName));
Validate(nameof(LastName));
}
public Person(string fName, string lName)
{
FirstName = fName;
LastName = lName;
}
#endregion Constructors
#region Command
public ICommand AddToList { get; set; }
bool CanAdd(object para) => _validationResults.Count == 0;
void Add(object para)
{
base.Add(new Person(FirstName, LastName));
FirstName = LastName = null;
}
#endregion Command
#region INotifyPropertyChanged
public new event PropertyChangedEventHandler PropertyChanged;
void OnPropertyChanged([CallerMemberName] string name = "") => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
#endregion INotifyPropertyChanged
#region INotifyDataErrorInfo
private readonly Dictionary<string, List<ValidationResult>> _validationResults = new Dictionary<string, List<ValidationResult>>();
public bool HasErrors => _validationResults.Count > 0;
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
public System.Collections.IEnumerable GetErrors(string propertyName)
{
if (_validationResults.TryGetValue(propertyName, out List<ValidationResult> validationResults))
return new string[1] { validationResults.First().ErrorMessage };
return null;
}
private void Validate([CallerMemberName]string propertyName = "")
{
var ValidationResults = new List<ValidationResult>();
if (Validator.TryValidateProperty(typeof(Person).GetProperty(propertyName).GetValue(this),
new ValidationContext(this) { MemberName = propertyName }, ValidationResults))
{
_validationResults.Remove(propertyName);
}
else
{
_validationResults[propertyName] = ValidationResults;
}
ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(propertyName));
}
#endregion
}