Your binding source must implement the IDataErrorInfo
interface. Then you can set the ValidatesOnDataErrors
and NotifyOnValidationError
propeties on the binding.
See a simplified example below.
A base class to handle property changes and validation.
internal abstract class ValidatedObservableBase : INotifyPropertyChanged, IDataErrorInfo
{
public event PropertyChangedEventHandler PropertyChanged;
public string this[string columnName]
{
get
{
var results = new List<ValidationResult>();
var valid = Validator.TryValidateProperty(GetType().GetProperty(columnName)?.GetValue(this), new ValidationContext(this) { MemberName = columnName }, results);
return valid ? null : results[0].ErrorMessage;
}
}
public string Error
{
get => null;
}
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
The model, derived from the above base class.
internal class Model : ValidatedObservableBase
{
private string number;
[Required(ErrorMessage = "Required error")]
[RegularExpression(@"^[\d]+", ErrorMessage = "Regex error")]
public string Number
{
get => number;
set
{
number = value;
OnPropertyChanged();
}
}
}
A simple view model to set as the window's DataContext
.
internal class ViewModel
{
public Model Model { get; set; } = new Model();
}
Lastly, the window.
<Window
...
xmlns:local="clr-namespace:Demo"
mc:Ignorable="d">
<Window.DataContext>
<local:ViewModel />
</Window.DataContext>
<StackPanel>
<TextBox
x:Name="TB"
Margin="24,24,24,0"
VerticalAlignment="Top"
Text="{Binding Path=Model.Number, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True, NotifyOnValidationError=True}" />
<TextBlock
Margin="24,4,24,24"
Foreground="Red"
Text="{Binding ElementName=TB, Path=(Validation.Errors)[0].ErrorContent}" />
</StackPanel>
</Window>
