Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ValidationRules not removing error when value is set back to valid value

INTRODUCTION

I have created a DecimalTextBox UserControl that houses some decimal validation I need done, so that I dont need to recreate the validation each time, and can just use the UserControl instead. This validation has properties that need to be bound to, and so I have created DependencyProperties so I can bind to them, according to this article by Josh Smith.


THE PROBLEM

The control's validation is behaving strangely. When I type an erroneous value into the TextBox, it shows up as an error. However when I try to change the value back in the code the value shown in the textbox remains unchanged.

Here are the steps I perform that cause this error (in this example 1 is an invalid value):

  1. Load the form and the default value is 0.
  2. Enter 1 into the textbox (and the textbox goes red due to the validation result being and error)
  3. In the code I set the property bound to the textbox to 0
  4. The form still displays 1 in a red textbox

CODE EXAMPLE

I prepaired an example demonstrating the problem, which can be downloaded here.

I'll post some of the code here, if you want more let me know.

ValidationTestControl's XAML

<UserControl x:Class="WPFTestProject.ValidationTestControl"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:v="clr-namespace:WPFTestProject"
    x:Name="ValidationTest"
    Height="50" Width="525">
<Grid>
    <Grid.RowDefinitions>
        <RowDefinition></RowDefinition>          

    </Grid.RowDefinitions>
    <StackPanel Orientation="Horizontal" VerticalAlignment="Center" HorizontalAlignment="Center">
        <TextBlock Text="Type 'Banana' here: "></TextBlock>
        <TextBox MinWidth="100">
            <TextBox.Text>
                <Binding ElementName="ValidationTest"  Path="Text" UpdateSourceTrigger="PropertyChanged" Mode="TwoWay" ValidatesOnDataErrors="True" ValidatesOnExceptions="True">
                    <Binding.ValidationRules>
                        <v:NotBananaValidationRule>
                            <v:NotBananaValidationRule.NotWhatBinding>
                                <v:NotBananaBinding x:Name="NotBananaValidationBinding"></v:NotBananaBinding>
                            </v:NotBananaValidationRule.NotWhatBinding>
                        </v:NotBananaValidationRule>
                    </Binding.ValidationRules>
                </Binding>
            </TextBox.Text>
        </TextBox>
        <TextBlock Text=" (the text will give error when = 'Banana')"></TextBlock>
    </StackPanel>
</Grid>

ValidationTestControls Code Behind

(yes I know not very MVVM but I felt it was ok for this stand alone control)

 public partial class ValidationTestControl : UserControl
{
    public ValidationTestControl()
    {
        InitializeComponent();
        Banana = "Banana";

        Binding BananaBinding = new Binding("Banana");
        BananaBinding.Source = this;

        NotBananaValidationBinding.SetBinding(NotBananaBinding.NotWhatProperty, BananaBinding);
    }

    public static DependencyProperty TextProperty = DependencyProperty.Register("Text", typeof(string), typeof(ValidationTestControl), new PropertyMetadata());
    public static DependencyProperty BananaProperty = DependencyProperty.Register("Banana", typeof(string), typeof(ValidationTestControl), new PropertyMetadata());

    public string Text
    {
        get
        {
            return (string)GetValue(TextProperty);
        }
        set
        {
            SetValue(TextProperty, value);
        }
    }


    public string Banana
    {
        get
        {
            return (string)GetValue(BananaProperty);
        }
        set
        {
            SetValue(BananaProperty, value);
        }
    }


}

ValidationRule and FrameWorkElement created for binding

 public class NotBananaValidationRule:ValidationRule
{
    private NotBananaBinding _notWhatBinding;
    public NotBananaBinding NotWhatBinding
    {
        get { return _notWhatBinding; }
        set { _notWhatBinding = value; }
    }

    public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
    {
        string what = value.ToString();

        if(what == _notWhatBinding.NotWhat||string.IsNullOrEmpty(what))
            return new ValidationResult(false,
                       "Please enter a string that is not " + _notWhatBinding.NotWhat);
        else
            return new ValidationResult(true, null);

    }

}


public class NotBananaBinding : FrameworkElement
{
    public static readonly DependencyProperty NotWhatProperty = DependencyProperty.Register(
      "NotWhat", typeof(string), typeof(NotBananaBinding), new UIPropertyMetadata());

    public string NotWhat
    {
        get { return (string)GetValue(NotWhatProperty); }
        set { SetValue(NotWhatProperty, value); }
    }

    public NotBananaBinding() { }
}

Basically what this code does is check if you have typed "Banana" and then returns a validation error. The control exposes dependency properties because I want to be able to bind to them when I use the control. The FrameworkElement NotBananaBinding lets me create dependency properties (because it is a DependencyObject so i can bind stuff for the validation. The ValidationRule has a NotBananaBinding property that stores the dependency property and uses it in the validate method.

I know my property names are kinda crappy, sorry. The thing is that the example does a good job of displaying the error. In my haste to make an example I didn't name the variables very well. If you find the code crappy please download the sample here.


WHAT I'VE FIGURED OUT SO FAR

Basically this problem seems to be caused by the fact that I am not actually changing the value.

Even if I call OnPropertyChanged on the property, because the value is not different it doesn't try and reevaluate the Validation.

I can obviously change the value to some arbitrary Valid value and then change it to the one I want it to be and it will work, but I was hoping there is some way to get call validation manually, to reevaluate the value and then change it etc. The changing it away and back is kinda messy.


CONCLUSION

Am I doing something wrong (perhaps something about the way I implemented the validation and binding from Josh Smiths post)

Is this just a c# bug, or is the behavior intended? If so then why?

Are there any elegant ways to fix it?

u_u

like image 481
Jason Ridge Avatar asked Nov 14 '22 09:11

Jason Ridge


1 Answers

The validation prevents the Text property to be set. Put a break point on the setter and you will see that it will not break when you type the last 'a'. If you type Bananan and hit Backspace and it errors, push the button and it will work. The validation makes sure that there can be no invalid value in your property. So if you save it to let's say a database while in error, it won't save an invalid value.

like image 75
Silvermind Avatar answered Nov 16 '22 03:11

Silvermind