5

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

Dave Clemmer
  • 3,741
  • 12
  • 49
  • 72
Jason Ridge
  • 1,868
  • 15
  • 27
  • 1
    Posting code really helps - people don't want to run sample projects. Try to reduce the problem to the minimum possible code that reproduces the problem. You never know, you might solve the problem yourself. – ChrisF Feb 20 '12 at 10:51

1 Answers1

1

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.

Dave Clemmer
  • 3,741
  • 12
  • 49
  • 72
Silvermind
  • 5,791
  • 2
  • 24
  • 44
  • I get that if you change the value in the textbox by typing another character in it the validation rule is reevaluated, but my problem is that I cant set it to a valid value _in code_. Because the validation prevents the binding from occuring, I cant seem to change the value in the textbox via code as the binding is removed. I try to set the value of the textbox back to the previous value but because the value of the property is _already that value_ (because the change to the invalid value was not allowed by the validation) there is no change notification and the textbox value isnt refreshed – Jason Ridge Mar 11 '12 at 10:56
  • How did you set the text on the textBox to get the binding removed, because I put a button next to the textbox and it's onclick event i set the text of the textBox to Banan and changing the value keeps triggering the setter. – Silvermind Mar 11 '12 at 13:09
  • did you download the sample? that pretty much explains it. What I did was type in Banana in the textbox. Then press the button to change the text back to Banan and it doesnt change because the value of the property is already Banan. And because it doesnt change, the text displayed in the textbox stays Banana. The binding is severed when the validation throws an error, because the text in the textbox displays Banana but the value is Banan – Jason Ridge Mar 11 '12 at 16:09
  • I did download otherwise I couldn't put a button next to the textbox. Set the value of the textbox not the value of the property. – Silvermind Mar 11 '12 at 16:21
  • Yea ok but the thing is in my real project im using MVVM so I can't access the value of the textbox. The sample shows how I can't do it through the code by changing the property. In reality this thing is a control without that button, and I try to set the text back in a viewmodel by using the property to which the control is bound. The textbox is inside the control so I have no direct access to it. I suppose I could expose a method to change it back, but I'm not sure if I like that idea. – Jason Ridge Mar 12 '12 at 05:44
  • I'm just curious but why would you have a validation while you want to alter the data if it's wrong? Maybe someone else knows the answer. – Silvermind Mar 12 '12 at 07:35
  • I want to make a clear button on the view model that resets the values back to their original state. Some of these "DecimalTextboxes" will have an invalid value, and the property they are bound to will have a value of 0. If I were to reset the values of the properties inside the viewmodel I would set them to 0, but the textbox inside the "DecimalTextBox" control will still display the invalid value because the property it is bound to is not changing. Also (for another very complex situation I feel is out of scope) the textboxes are initialized with errors already and I want to get rid of them. – Jason Ridge Mar 12 '12 at 11:52
  • you have `string.IsNullOrEmpty(what)` in the validationrule which causes the textboxes to launch in error state, replace it with `|| what == null`. Nevertheless I cannot help you with this problem. I think I'll dive into MVVM. The only thing you can do in your situation is as you already did: setting the Text = `""`. I'm sorry I couldn't be of more help. – Silvermind Mar 12 '12 at 12:51
  • Thanks alot for the interest. At the moment i am changing the value to a valid value then changing it back in code, as this seems to work, but I hate it cause its messy. Anyway I do have a work around it was more of a "why is this working this way" kind of thing. I'll give the `what == null` thing a try. Again thanks for trying, you are the only one who has in almost a month since this was posted. – Jason Ridge Mar 12 '12 at 13:33
  • Hi @ExitMusic - I have a similar problem (see my question about WPF controls with “invalid” values). Did you ever find a resolution to this? I don't have access to the control so I can't call a method on it to reset the value. I tried playing around with DependencyProperty.UnsetValue but this gets treated as null for all intents & purposes. Be interested to hear how you got around this. I was also doing the messy change to valid value then back to null but I hate it. – Stephen Drew Apr 25 '12 at 11:47