2

Binding is so powerful in WPF. Supposed that we have a Number property (nullable int) and is bound to a textbox.

I realized when it throws an error, the property has the last correct value.

I mean these are the processes:

TEXTBOX: ""     PROPERTY: null
TEXTBOX: "2"    PROPERTY: 2
TEXTBOX: "2b"   PROPERTY: 2   <-- here is the problem, should be null instead 2(by the error)

Is there a way which the binding set a null value when it produce an error?

Some persons told me I need to implement IDataErrorInfo, but I guess that interface is to validate business rules. So I wouldn't prefer user it.

UPDATE:

    <TextBox Text="{Binding Number, UpdateSourceTrigger=PropertyChanged,
        ValidatesOnExceptions=True, ValidatesOnDataErrors=True,
        NotifyOnValidationError=True, TargetNullValue={x:Static sys:String.Empty}}"
Darf Zon
  • 6,268
  • 20
  • 90
  • 149

3 Answers3

4

You are using UpdateSourceTrigger=PropertyChanged, which means that anytime the user hits a key, it is storing the data in your data context

For example, user types 2, then your property is equal to "2". User types b and it will attempt to replace "2" with "2b", which fails, so the original property of "2" remains.

Remove the UpdateSourceTrigger and it will revert to the default of LostFocus, which means it will only update the property when the TextBox loses focus.

You could set the property to null when an error is produced, but I would not recommend doing that because then if a user accidently hits the wrong key, the TextBox would get cleared.

As a side note, use IDataErrorInfo for all validation, not just business rule validation. WPF is built to work with it. My Models use it to verify their data is the correct length, type, etc, and my ViewModels use it to verify that business rules are being followed

Edit

The alternative suggestion I would have would be to bind to a string value, not a number field. This way when the value changes, you can try and cast it to your Int and return an error if it can't be cast.

public class SomeObject : IDataErrorInfo
{
    public string SomeString { get; set; }
    public Int32? SomeNumber { get; set; }

    #region IDataErrorInfo Members

    public string Error
    {
        get { throw new NotImplementedException(); }
    }

    public string this[string columnName]
    {
        get
        {
            if (columnName == "SomeString")
            {
                int i;
                if (int.TryParse(SomeString, i))
                {
                    SomeNumber = i;
                }
                else
                {
                    SomeNumber = null;
                    return "Value is not a valid number";
                }
            }
            return null;
        }
    }

    #endregion
}
Rachel
  • 130,264
  • 66
  • 304
  • 490
  • The last statement makes sense. So, do you think is convenient use something like a converter? – Darf Zon Jan 26 '12 at 16:50
  • @DarfZon For what? Converters are meant to be used to convert one data type to another, so I fail to see what you're trying to accomplish with one. – Rachel Jan 26 '12 at 17:00
  • All right, one doubt. At this case, how can I use IDataErrorInfo, because if I set manually null value to Number property, the textBox will update (and should show the incorrect value) – Darf Zon Jan 26 '12 at 17:07
  • @DarfZon Would it work to just remove `UpdateSourceTrigger="PropertyChanged"` from your binding like I suggested in my answer? It will make it so the property only gets updated when the TextBox loses focus, meaning they finished typing. – Rachel Jan 26 '12 at 17:16
  • Why exactly do you want to use IDataErrorInfo? – Security Hound Jan 26 '12 at 17:18
  • @DarfZon The alternative suggestion I would have would be to bind to a string value, and on Save check if the string can be correctly parsed into your `Int?` field. If so, store it in the `Int?` field and save it. If not, leave the field `null` and save it. If you want to show a validation error, then implement `IDataErrorInfo` and return an error if the string can't be parsed into the `Int?` field. – Rachel Jan 26 '12 at 17:18
  • @Ramhound Because that is what WPF uses for Validation, and I assumed he was using WPF's validation system since the binding includes `ValidatesOnDataErrors=True` – Rachel Jan 26 '12 at 17:19
  • @Rachel I can't understand how removing `UpdateSourceTrigger="PropertyChanged"` property can help me. When lose the focus, it still maintains the last correct value. – Darf Zon Jan 26 '12 at 17:40
  • @DarfZon I wasn't sure if that was the behavior you wanted. I added an update to my answer which will show an error and store `null` in your int field if the value cannot be cast as an `int`, without actually resetting what the user types if they hit a wrong key. – Rachel Jan 26 '12 at 18:11
  • @Rachel this doesn't feel right to me - I'm sure the validation mechanism shouldn't be setting values. How about implementing the string property so that it returns `this.SomeNumber.ToString()` in the getter and sets `this.SomeNumber` in the setter based on an `int.Parse`? At least that way the data is only being stored in one variable. You could still validate the string field in that case and display the message – Steve Greatrex Jan 26 '12 at 20:22
  • @SteveGreatrex Well the question is how to set a value to `null` if the value doesn't pass validation, but not reset the value if it doesn't pass validation. The only way I can think to do that is with two properties (since a value cannot be both `null` and an invalid value at the same time). The only issue I have with setting a value in the validation of an object would be that the validation might not get run. Personally I would probably implement setting the int value in the `Save` method or in the `PropertyChanged` event, but I was trying to keep this simple. – Rachel Jan 26 '12 at 20:42
  • @Rachel looking at the question, his comment after setting the textbox to "2b" seems to suggest that he *does* want to reset the value to null if an invalid one is entered. I agree that the risk of the validation not being run is what makes me uncomfortable with the suggested solution, but you get the same problem if the (hypothetical) `Save` method doesn't get run. I'd always go for either the property setter or (at a stretch) the property changed handler - at least that way the view model is always in a consistent state – Steve Greatrex Jan 26 '12 at 20:54
  • @SteveGreatrex I suppose if he wanted the value to get reset if it was invalid, I would add that kind of logic in the `PropertyChanged` event (I personally hate leaving logic in my getters/setters). I was thinking he didn't want to reset the value because at the time his first comment posted (says `that last statement makes sense`), the "last statement" in my answer was that I wouldn't recommend setting it to `null` because if someone hit the wrong key it would reset the text in the TextBox. Honestly, I had a hard time understanding exactly what the OP was trying to accomplish. – Rachel Jan 26 '12 at 21:00
1

I think that the simplest way to get that behaviour is to use an IValueConverter to convert from string to int?:

public class NullableIntConverter : IValueConverter
{
    public static NullableIntConverter Instance = new NullableIntConverter();
    public void ConvertBack(object value, ...)
    {
        int intValue = 0;
        if (int.TryParse((string)value, out intValue))
            return intValue;

        return null;
    }

    public void Convert(object value, ...)
    {
        return value.ToString();
    }
}

Then you can specify this in your binding as below (where local is mapped to your converter namespace):

<TextBox Text="{Binding Number, Converter="{x:Static local:NullableIntConverter.Instance}" ... />
Steve Greatrex
  • 15,789
  • 5
  • 59
  • 73
  • For that to work you'd need to put your logic in `ConvertBack` since that is what runs when trying to update the property backing the binding, and I don't think it will show validation errors. – Rachel Jan 26 '12 at 18:15
  • @Rachel good point on the conversion direction, and you're right: it won't show validation errors as there would no longer be any validation errors - every value set on the property would be valid. OP doesn't mention validation messages so I'm assuming this is ok – Steve Greatrex Jan 26 '12 at 20:18
  • I had assumed he cared about validation since the binding contained `ValidatesOnDataErrors="True"`, but if that's not the case, and if he didn't care that the TextBox's text resets when an invalid value is entered, then this would probably work fine. I think. I'd want to test it to be sure, because `null` is a valid value for the data object, but not for the Converter :) – Rachel Jan 26 '12 at 21:01
  • @Rachel yeah, it needs null-checking but I was going for brevity :) – Steve Greatrex Jan 26 '12 at 21:15
0

It gets more powerful yet. You probably ought to go the route of validation via the interface/binding itself - WPF has built-in support for this, examples of which can be found in the Data Binding Overview over at MSDN.

An example of implementing this could go as follows:

<...>
  <Binding.ValidationRules>
    <ExceptionValidationRule />
  </Binding.ValidationRules>
</...>

The linked documentation covers quite a bit on the topic of binding, so here is an excerpt from the relevant section 'Data Validation':

A ValidationRule object checks whether the value of a property is valid.

A ExceptionValidationRule checks for exceptions thrown during the update of the binding source property. In the previous example, StartPrice is of type integer. When the user enters a value that cannot be converted to an integer, an exception is thrown, causing the binding to be marked as invalid. An alternative syntax to setting the ExceptionValidationRule explicitly is to set the ValidatesOnExceptions property to true on your Binding or MultiBinding object.

Grant Thomas
  • 44,454
  • 10
  • 85
  • 129
  • I do not know how this can help me. I'm really using valdation rules with this attribute: ValidatesOnExceptions=True – Darf Zon Jan 26 '12 at 16:49