simple way to add a custom validation rule to all textboxes

5.8k views Asked by At

I'm having problems setting a custom validation rule for all my textboxes in a Page.

I know how to set it for 1 single textbox:

<TextBox Margin="79,118,79,121" Name="textBox1" Style="{Binding EmptyTextboxValidator, UpdateSourceTrigger=PropertyChanged}">
    <TextBox.Text>
        <Binding Path="Text" UpdateSourceTrigger="PropertyChanged">
            <Binding.ValidationRules>
                <validators:StringRangeValidationRule MinimumLength="1" ErrorMessage="Text is te kort" />
            </Binding.ValidationRules>
        </Binding>
    </TextBox.Text>
</TextBox>

However, if i need to add this code for every single textbox i'm not sure this is as useful since it would be an awful lot of copy pasting. There must be an easier way right?

if been doing a lot of searching but i couldn't find an example to set it for all textboxes, only examples to set it for 1 single textbox (as shown above)

here's the code for the textbox style:

<Application.Resources>
    <Style x:Key="EmptyTextboxValidator" TargetType="{x:Type TextBox}">
        <Setter Property="Validation.ErrorTemplate">
            <Setter.Value>
                <ControlTemplate>
                    <DockPanel LastChildFill="True">
                        <Border BorderBrush="Red" BorderThickness="3">
                            <AdornedElementPlaceholder Name="MyAdorner" />
                        </Border>
                    </DockPanel>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
        <Style.Triggers>
            <Trigger Property="Validation.HasError" Value="true">
                <Setter Property="ToolTip" 
                        Value="{Binding RelativeSource={RelativeSource Self}, 
                        Path=(Validation.Errors)[0].ErrorContent}"/>
            </Trigger>
        </Style.Triggers>
    </Style>
</Application.Resources>

and the code for the validation rule:

using System;
using System.Windows.Controls;
using System.Globalization;

namespace MyValidators
{
    public class StringRangeValidationRule : ValidationRule
    {
        private int _minimumLength = 1;
        private string _errorMessage;

        public int MinimumLength
        {
            get { return _minimumLength; }
            set { _minimumLength = value; }
        }

        public string ErrorMessage
        {
            get { return _errorMessage; }
            set { _errorMessage = value; }
        }

        public override ValidationResult Validate(object value, CultureInfo cultureInfo)
        {
            ValidationResult result = new ValidationResult(true, null);
            string inputString = (value ?? string.Empty).ToString();
            if (string.IsNullOrEmpty(inputString) || inputString.Length < MinimumLength)
            {
                result = new ValidationResult(false, this.ErrorMessage);
            }
            return result;
        }
    }
}

the viewModel:

public class Data : INotifyPropertyChanged
{
    private int id = -1;
    private bool updated = true;
    private string text;
    private string name;

    public int ID
    {
        get
        {
            return id;
        }
        set
        {
            id = value;
            OnPropertyChanged("ID");
        }
    }

    public bool Updated
    {
        get
        {
            return updated;
        }
        set
        {
            updated = value;
        }
    }

    public string Text
    {
        get
        {
            return text;
        }
        set
        {
            text = value;
            OnPropertyChanged("Text");
        }
    }

    public string Name
    {
        get
        {
            return name;
        }
        set
        {
            name = value;
            OnPropertyChanged("Name");
        }
    }

    #region INotifyPropertyChanged Members

    public event PropertyChangedEventHandler PropertyChanged;
    protected void OnPropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
        updated = true;
    }
    #endregion
}

Also my knowledge of xaml is limited and this code was part of a test i made to get some more understanding of xaml.

Also might there be a way to just add field to the textbox for example:

<TextBox ValidateStringLength="true"/>

or even better if i could just give it an integer parameter for the minimum length of the string:

<TextBox ValidateStringLength="5"/>

Either one of the 2 (setting it as i'm doing but for all textboxes at once) or the example i demonstrated above would be really appreciated.

1

There are 1 answers

6
dev hedgehog On

The solution is called BindingGroup. Take a look at this code:

<StackPanel Name="stackPanel1">
  <StackPanel.BindingGroup>
    <BindingGroup NotifyOnValidationError="True">
      <BindingGroup.ValidationRules>
        <src:ValidateDateAndPrice ValidationStep="ConvertedProposedValue" />
      </BindingGroup.ValidationRules>
    </BindingGroup>
  </StackPanel.BindingGroup>

  <HeaderedContentControl Header="Price">
    <TextBox Name="priceField"  Width="150">
      <TextBox.Text>
        <Binding Path="Price" Mode="TwoWay" />
      </TextBox.Text>
    </TextBox>
  </HeaderedContentControl>

  <HeaderedContentControl Header="Date Offer Ends">
    <TextBox Name="dateField" Width="150" >
      <TextBox.Text>
        <Binding Path="OfferExpires" StringFormat="d" />
      </TextBox.Text>
    </TextBox>
  </HeaderedContentControl>

  <StackPanel Orientation="Horizontal">
    <Button IsDefault="True" Click="Submit_Click">_Submit</Button>
    <Button IsCancel="True" Click="Cancel_Click">_Cancel</Button>
  </StackPanel>
</StackPanel>

All the TextBoxes have ValidateDateAndPrice ValidationRule available.

And you can also control when changes shall be saved.

private void Submit_Click(object sender, RoutedEventArgs e)
{
    if (stackPanel1.BindingGroup.CommitEdit())
    {
        MessageBox.Show("Item submitted");
        stackPanel1.BindingGroup.SubmitEdit();
    }
}

In this case changes are being submitted when button clicked.

Edit:

I will give you an example how to make it work. I used your code to create the example.

<Window>
    ....
    <Window.Resources>
        <Style TargetType="{x:Type TextBox}">
            <Setter Property="Validation.ErrorTemplate">
                <Setter.Value>
                    <ControlTemplate>
                        <DockPanel LastChildFill="True">
                            <Border BorderBrush="Blue" BorderThickness="3">
                                <AdornedElementPlaceholder Name="MyAdorner" />
                            </Border>
                        </DockPanel>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
            <Style.Triggers>
                <Trigger Property="Validation.HasError" Value="true">
                    <Setter Property="ToolTip" 
                        Value="{Binding RelativeSource={RelativeSource Self}, 
                        Path=(Validation.Errors)[0].ErrorContent}"/>
                </Trigger>
            </Style.Triggers>
        </Style>
    </Window.Resources>

    <Window.BindingGroup>
        <BindingGroup>
            <BindingGroup.ValidationRules>
                <local:StringRangeValidationRule MinimumLength="5" ErrorMessage="Text too long!" />
            </BindingGroup.ValidationRules>
        </BindingGroup>
    </Window.BindingGroup>

    <Grid Margin="10" >
        <TextBox Margin="100, 5, 0, 0" HorizontalAlignment="Left" VerticalAlignment="Top"
                 Text="{Binding Name}" Validation.ValidationAdornerSiteFor="{Binding ElementName=window}">
        </TextBox>
        <Button Content="Save" Click="Button_Click" Margin="0, 5, 100, 0" HorizontalAlignment="Right" VerticalAlignment="Top"/>
    </Grid>
</Window>

You need to have your templates and styles in window to make it work.

This is the code behind:

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        this.DataContext = new Data() { Name = "Hello World!" };
    }

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        window.BindingGroup.CommitEdit();
    }
}

public class Data : INotifyPropertyChanged
{
    private int id = -1;
    private bool updated = true;
    private string text;
    private string name;

    public int ID
    {
        get
        {
            return id;
        }
        set
        {
            id = value;
            OnPropertyChanged("ID");
        }
    }

    public bool Updated
    {
        get
        {
            return updated;
        }
        set
        {
            updated = value;
        }
    }

    public string Text
    {
        get
        {
            return text;
        }
        set
        {
            text = value;
            OnPropertyChanged("Text");
        }
    }

    public string Name
    {
        get
        {
            return name;
        }
        set
        {
            name = value;
            OnPropertyChanged("Name");
        }
    }

    #region INotifyPropertyChanged Members

    public event PropertyChangedEventHandler PropertyChanged;
    protected void OnPropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
        updated = true;
    }

    #endregion
}

public class StringRangeValidationRule : ValidationRule
{
    private int _minimumLength = 1;
    private string _errorMessage;

    public int MinimumLength
    {
        get { return _minimumLength; }
        set { _minimumLength = value; }
    }

    public string ErrorMessage
    {
        get { return _errorMessage; }
        set { _errorMessage = value; }
    }

    public override ValidationResult Validate(object value, CultureInfo cultureInfo)
    {
        BindingGroup group = (BindingGroup)value;
        var item = group.Items[0];
        string inputString = (group.GetValue(item, "Name") ?? string.Empty).ToString();
        if (true || string.IsNullOrEmpty(inputString) || inputString.Length < MinimumLength)
        {
            return new ValidationResult(false, this.ErrorMessage);
        }

        return ValidationResult.ValidResult;
    }
}

As you can see I changed the validation rule and I added Validation.ValidationAdornerSiteFor to indicate the TextBox should display error.

You should be able to copy and paste this and it will work.