Little Programmer
Little Programmer

Reputation: 191

How to use data annotations to create an input-validation for a textbox in WPF?

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 : enter image description here

Upvotes: 1

Views: 3525

Answers (3)

lokusking
lokusking

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

slugster
slugster

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:

enter image description here

Reference my blog post Taking data binding, validation and MVVM to the next level

Upvotes: 3

AnjumSKhan
AnjumSKhan

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>

Data Validation in WPF

Upvotes: 2

Related Questions