Reputation: 191
I want to display a input-validation for the user when entering empty values in a text box, is there a simple way to use data annotation to do this for WPF ?
I am new to WPF and C# if someone could explain this for me I would really appreciate it.
I want something like this or similar :
Upvotes: 1
Views: 3525
Reputation: 7456
Beside all the other correct answers here, additionally to ValidationRule
, I'd recommend you to use IDataError
-Interface.
With that interface you can match more easily your use of DataAnnotations as you mentioned in the title.
Demo-Model:
public class Pony : IDataErrorInfo, INotifyPropertyChanged {
private Dictionary<string, List<string>> _errors = new Dictionary<string, List<string>>();
[Range(0,4)]
public int Id {
get; set;
}
[Required]
public string Name {
get; set;
}
public Brush Color {
get; set;
}
public string Error {
get {
var builder = new StringBuilder();
foreach (var error in this.Errors) {
if (error.Value.Count > 0) {
foreach (var text in error.Value) {
builder.AppendLine(text);
}
}
}
return builder.Length > 0 ? builder.ToString(0, builder.Length - 2) : builder.ToString();
}
}
public bool HasError => this.Errors.Count > 0;
public virtual string this[string columnName] {
get {
var modelClassProperties = TypeDescriptor.GetProperties(this.GetType());
foreach (PropertyDescriptor prop in modelClassProperties) {
if (prop.Name != columnName) {
continue;
}
this.Errors[columnName] = new List<string>();
foreach (var attribute in prop.Attributes) {
if (!(attribute is ValidationAttribute)) {
continue;
}
var validation = attribute as ValidationAttribute;
if (validation.IsValid(prop.GetValue(this))) {
continue;
}
var dn = prop.Name;
foreach (var pa in prop.Attributes.OfType<DisplayNameAttribute>()) {
dn = pa.DisplayName;
}
this.Errors[columnName].Add(validation.FormatErrorMessage(dn));
this.OnPropertyChanged("Error");
return validation.FormatErrorMessage(dn);
}
}
this.Errors.Remove(columnName);
this.OnPropertyChanged("Error");
return null;
}
}
internal Dictionary<string, List<string>> Errors => this._errors;
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) {
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Demo-XAML
<Grid VerticalAlignment="Stretch" HorizontalAlignment="Stretch" Height="200">
<Grid.Resources>
<Style TargetType="{x:Type TextBox}">
<Setter Property="MinWidth" Value="100"></Setter>
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<StackPanel>
<!-- Placeholder for the TextBox itself -->
<AdornedElementPlaceholder x:Name="textBox"/>
<TextBlock Text="{Binding [0].ErrorContent}" Foreground="Red"/>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style TargetType="{x:Type WrapPanel}">
<Setter Property="Margin" Value="0,0,0,10"></Setter>
</Style>
</Grid.Resources>
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<GroupBox Header="Pony 1" Grid.Row="1">
<StackPanel>
<WrapPanel>
<TextBlock Text="Id:"/>
<TextBox Text="{Binding Ponys[0].Id, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}"></TextBox>
</WrapPanel>
<WrapPanel Grid.Row="1">
<TextBlock Text="Name:"/>
<TextBox Text="{Binding Ponys[0].Name, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}"></TextBox>
</WrapPanel>
<WrapPanel Grid.Row="2">
<TextBlock Text="Color:"/>
<TextBox Text="{Binding Ponys[0].Color, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}"></TextBox>
</WrapPanel>
</StackPanel>
</GroupBox>
<GroupBox Header="Pony 2">
<StackPanel>
<WrapPanel>
<TextBlock Text="Id:"/>
<TextBox Text="{Binding Ponys[1].Id, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}"></TextBox>
</WrapPanel>
<WrapPanel Grid.Row="1">
<TextBlock Text="Name:"/>
<TextBox Text="{Binding Ponys[1].Name, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}"></TextBox>
</WrapPanel>
<WrapPanel Grid.Row="2">
<TextBlock Text="Color:"/>
<TextBox Text="{Binding Ponys[1].Color, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}"></TextBox>
</WrapPanel>
</StackPanel>
</GroupBox>
</Grid>
Demo-Usage:
public MainWindow() {
InitializeComponent();
this.Ponys = new List<Pony>();
this.Ponys.Add(new Pony() { Color = Brushes.HotPink });
this.Ponys.Add(new Pony() { Id = 9, Name = "Not so fluffy", Color = Brushes.Chocolate });
this.DataContext = this;
}
Conclusion:
This Approach is designed to use in a Base
-class.
It fully supports Attribute-Validation and is capable of handling multiple Attributes per Property.
Since ValidationAttribute
is not sealed, you can inherit from it and design your own validations.
Example:
[AttributeUsage(AttributeTargets.Property, AllowMultiple = true)]
public class Numeric : ValidationAttribute {
protected override ValidationResult IsValid(object value, ValidationContext validationContext) {
if (value == null) {
return ValidationResult.Success;
}
double result;
var isNumeric = double.TryParse(value.ToString(), out result);
return !isNumeric ? new ValidationResult(this.ErrorMessage) : ValidationResult.Success;
}
}
I developed this, because i hated how i had to use the ValidationRules
.
Additionally, the ValidationRules
are only Input-Validations. This means, if your data is corrupt, you will never notice
Upvotes: 0
Reputation: 49984
Using a ValidationRule on your binding can get the effect you want, you specify them declaratively in your XAML, and you can make them as custom and complex as you need to:
<TextBox x:Name="FilePathTextBox" Width="350" Margin="5,0,0,0">
<TextBox.Text>
<Binding Path="FilePath" UpdateSourceTrigger="PropertyChanged" >
<Binding.ValidationRules>
<this:FilePathValidationRule />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
will produce this:
Reference my blog post Taking data binding, validation and MVVM to the next level
Upvotes: 3
Reputation: 9827
You have to use ErrorTemplate
like below :
<TextBox...>
<Validation.ErrorTemplate>
<ControlTemplate>
<StackPanel>
<!-- Placeholder for the TextBox itself -->
<AdornedElementPlaceholder x:Name="textBox"/>
<TextBlock Text="{Binding [0].ErrorContent}" Foreground="Red"/>
</StackPanel>
</ControlTemplate>
</Validation.ErrorTemplate>
</TextBox>
Upvotes: 2