答案 0 :(得分:2)
你必须使用ErrorTemplate
,如下所示:
<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>
答案 1 :(得分:2)
在绑定上使用ValidationRule可以获得所需的效果,您可以在XAML中以声明方式指定它们,并且可以根据需要将它们设置为自定义和复杂:
<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>
会产生这个:
参考我的博文Taking data binding, validation and MVVM to the next level
答案 2 :(得分:0)
此处除了ValidationRule
之外的所有其他正确答案,我建议您使用IDataError
- 界面。
使用该界面,您可以更轻松地匹配您在标题中提到的DataAnnotations的使用。
<强>演示 - 型号:强>
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));
}
}
<强>演示-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>
<强>演示-用法:强>
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;
}
<强>结论:强>
此方法旨在用于Base
- 类。
它完全支持属性验证,并且能够处理每个属性的多个属性。
由于ValidationAttribute
未密封,您可以从中继承并设计自己的验证。
示例:
[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;
}
}
我开发了这个,因为我讨厌我必须使用ValidationRules
。
此外,ValidationRules
仅为输入验证。这意味着,如果您的数据已损坏,您将永远不会注意到