如何使用数据注释为WPF中的文本框创建输入验证?

时间:2016-07-13 10:46:49

标签: c# wpf xaml

我想在文本框中输入空值时为用户显示输入验证,是否有一种简单的方法可以使用数据注释为WPF执行此操作? 我是WPF和C#的新手,如果有人能为我解释一下,我会非常感激。 我想要这样或类似的东西: enter image description here

3 个答案:

答案 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>

Data Validation in WPF

答案 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>

会产生这个:

enter image description here

参考我的博文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仅为输入验证。这意味着,如果您的数据已损坏,您将永远不会注意到