views:

90

answers:

2

Hi,

I am looking for a simple solution to the following problem:

I am using a simple TextBox control with the Text property bound to a property in the code behind. Additionally I am using a validation rule to notify the user of malformed input.

... error display style here ...

Now after entering valid data into the TextBox the user can hit a button to send the data. When clicking the button the data from the bound property UserName in the code behind is evaluated and sent.

The problem is that a user can enter valid data into the TextBox and this will be set in the property UserName. If the user then decides to change the text in the TextBox and the data becomes invalid, the setter of the property UserName is not called after the failed validation.

This means that the last valid data remains in the property UserName, while the TextBox display the invalid data with the error indicator. If the user then clicks on the button to send the data, the last valid data will be sent instead of the current TextBox content.

I know I could deactivate the button if the data is invalid and in fact I do, but the method is called in the setter of UserName. And if that is not called after a failed validation the button stays enabled.

So the question is: How do I enable calling of the property setter after a failed validation?

+1  A: 

You could set the ValidationRule.ValidationStep property for your validation rules to ValidationStep.UpdatedValue. This first updates the source, and then performs validation. That means, your property setter should be called even though your validation fails. Note that this property is only available from .NET 3.5 SP1 upwards. For more details, see this blog post (paragraph "How do I use it? (Part 1)").

gehho
Hi,thanks for this hint. It seems to work only partly. The setter is called, but the validation error doesn't disappear, even if valid data is entered...
HA
I cannot imagine that this is the default behaviour, so this might be caused by your implementation. But without any code, it is hard to help you... Especially because you seem to validate your input twice: once using a ValidationRule, and once in the property setter, as I understand from your comment to Jehof's question above!?
gehho
Yes, that is true and not the best design, I know.That aside the Validate method of the validation rule receives a BindingExpression object, if I set ValidationStep="UpdatedValue". If this is not set, Validate receives the TextBox.Text content as a string.
HA
I would not say it is bad design to have two validation stages. In my eyes, it absolutely makes sense to check the entered value for the correct type (e.g. only numeric) in a ValidationRule and check the rest in the property setter. I just meant that it is hard to find the problem in such a situation if you do not provide any code. However, this seems to be very ValidationStep-related and I have to admit that I am not very familiar with it. I just knew it is out there, and thought I'd give you a hint. I hope you can find a solution yourself. There should be enough information on the web.
gehho
Ok, thanks so far. I'll read the blog post from your answer tonight. Maybe that'll help me.
HA
Good luck! And if you find a solution, remember to post it here, so others get to know as well. Thanks!
gehho
From what I read the BindingGroup features is not what I need, because I don't want to use transactional editing.That leaves me with the BindingExpression that is received by the Validate method. So far I found out that the value of the TextBox is in BindingExpression.SourceValue. Unfortunately that property is private and I haven't found another way to access it, yet...
HA
I resolved the issue by using IDataErrorInfo and command binding with Executed and CanExecute.
HA
A: 

How I handle this in my view model classes:

public class MyViewModel : INotifyPropertyChanged, IDataErrorInfo
{
   private Dictionary<string, string> _Errors = new Dictionary<string, string>();

   public object SomeProperty
   {
      get { return _SomeProperty; }
      set
      {
         if (value != _SomeProperty && !ValidationError("SomeProperty", value))
            _SomeProperty = value;
            OnPropertyChanged("SomeProperty");
         }
      }
   }

   private bool ValidationError(string propertyName, object value)
   {
      // I usually have a Dictionary<string, Func<object, string>> that maps property
      // names to validation functions; the functions return null if the property
      // is valid and an error message if not.  You can embed the validation logic
      // in the property setters, of course, but breaking them out as separate methods
      // eases testing.
      _Errors[propertyName] = _ValidationMethods[propertyName](value);
      OnPropertyChanged("IsValid");
   }

   public bool IsValid
   {
      get { return !(_Errors.Where(x => x.Value != null).Any()));
   }

   public string this[string propertyName]
   {
      get
      {
         return (_Errors.ContainsKey(propertyName))
            ? _Errors[propertyName]
            : null;
      }
   }
}

It's a little awkward to get this all set up at first, but once you've done it, you have a simple and straightforward way to report validation errors to the UI (via the DataErrorValidationRule), a straightforward way to know whether any given property is valid or not (check _Errors), and an IsValid property that tells you whether or not the whole view model is valid. (Also, you can extend the IsValid property to handle the case where all the properties of the view model are valid but the view model itself is not, e.g. two mutually exclusive flags are both set.) And as long as you make them internal, the validation methods can be unit tested via NUnit or whatever.

I should add that the above code is off the top of my head and may or may not work as written - my actual working code is in a base class and has a lot of other things baked into it that would just be confusing.

Robert Rossney
Thanks, Robert. That does look interesting and I'll have a closer look at it tomorrow. So far I am still looking for a simple solution using the existing code.
HA