Silverlight ValidationSummary does not save errors for ListBox items with the same name

Overview:

I have a Silverlight 4 application where I see the problematic behavior of the ValidationSummary control when used with a ListBox and hope someone can help me.

At a high level, I have a ListBox where each row is defined (via ItemTemplate) with a text field named "txtInput". The text field is bound (TwoWay) to the property of the object marked with the DataAnnotation.Range attribute. ListBox is bound to a set of these objects. The same parent control also has a ValidationSummary element.

Scenario:

Imagine a case where there are two or more objects in a collection of items. The user will see a ListBox with several rows, each of which contains a text field. If the user enters invalid data in the first text field, a ValidationException is thrown as expected, and the ValidationSummary element displays an error as expected. The text box also gets the validation style (red frame).

Then, if the user enters incorrect data into the second text field of the line (without fixing data in the first text field), the second text field also throws a ValidationException and receives a validation pattern (red border), as expected, HOWEVER, the ValidationSummary element shows only one instance of the error message.

Then, if the user corrects one (but not both) invalid text entries, the fixed text field deletes the validation styles (red frame) and the ValidationSummary field leaves (this means that it considers that all validation errors have been resolved and has .HasErrors set to false). The second (still invalid) text field still applies the styling of the validation error (red frame).

My expectation is that the still remaining invalid text box will cause the ValidationSummary control to display further. My assumption is that the ValidationSummary control simply tracks failures by the property name, and after a successful attempt to set the property of that name, it clears the error marker (i.e.: it does not take into account the case when multiple instances with the same name).

Required Result:

Ultimately, what I'm trying to do does not allow the user to click the "Save" button on the screen in the presence of invalid data. I am currently doing this by binding the IsEnabled property of a button to the HasErrors property for ValidationSummary, but this does not work if ValidationSummary shows the above behavior.

Can someone tell me how to make the ValidationSummary element observe multiple errors of the same (repeating) text field or provide a viable alternative way to disable the Save button when these failures exist? (note: in my actual application, each line has several input controls, so any solution should consider this)

XAML fragment:

     <sdk: ValidationSummary x: Name = "valSummary" /> <ListBox ItemsSource = "{Binding DomainObjectCollection, Mode = TwoWay, ValidatesOnDataErrors = True, ValidatesOnNotifyDataErrors = True, ValidatesOnExceptions = true, NotifyOnVtidEm_Team>" true " DataTemplate> <TextBox Name = "txtInput" Text = "{Binding DecimalValue, Mode = TwoWay, ValidatesOnDataErrors = True, ValidatesOnNotifyDataErrors = True, ValidatesOnExceptions = true, NotifyOnValidationError = true}" /> <Itemplate> / ListBox> <Button x: Name = "btnSave" Content = "Save" Command = "{Binding SaveButtonCommand}" IsEnabled = "{Binding HasErrors, ElementName = valSummary, Converter = {StaticResource NotBoolConverter}}" /> 

Classes of domain objects:

[System.Runtime.Serialization.CollectionDataContractAttribute()] public partial class DomainObjectCollection : System.Collections.ObjectModel.ObservableCollection<DomainObject> { } [System.Runtime.Serialization.DataContractAttribute()] public partial class DomainObject : System.ComponentModel.INotifyPropertyChanged, System.ComponentModel.IDataErrorInfo, System.ComponentModel.INotifyDataErrorInfo { private int DomainObjectId_BackingField; private decimal DecimalValue_BackingField; private System.Collections.Generic.Dictionary<string, System.Collections.Generic.List<object>> _errors; [System.Runtime.Serialization.DataMemberAttribute()] public virtual int DomainObjectId { get { return this.DomainObjectId_BackingField; } set { if (!DomainObjectId_BackingField.Equals(value)) { this.DomainObjectId_BackingField = value; this.RaisePropertyChanged("DomainObjectId"); } } } [System.Runtime.Serialization.DataMemberAttribute()] [System.ComponentModel.DataAnnotations.RangeAttribute(typeof(decimal), "0", "100", ErrorMessage = "Value must be from 0 to 100.")] public virtual decimal DecimalValue { get { return this.DecimalValue_BackingField; } set { if (!DecimalValue_BackingField.Equals(value)) { this.DecimalValue_BackingField = value; this.RaisePropertyChanged("DecimalValue"); } } } string System.ComponentModel.IDataErrorInfo.Error { get { return string.Empty; } } string System.ComponentModel.IDataErrorInfo.this[string propertyName] { get { var results = Validate(propertyName); return results.Count == 0 ? null : string.Join(System.Environment.NewLine, results.Select(x => x.ErrorMessage)); } } private System.Collections.Generic.Dictionary<string, System.Collections.Generic.List<object>> Errors { get { if (_errors == null) _errors = new System.Collections.Generic.Dictionary<string, System.Collections.Generic.List<object>>(); return _errors; } } bool System.ComponentModel.INotifyDataErrorInfo.HasErrors { get { return Errors.Count > 0; } } public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged; public event System.EventHandler<System.ComponentModel.DataErrorsChangedEventArgs> ErrorsChanged; protected internal void RaisePropertyChanged(string propertyName) { if (PropertyChanged != null) { PropertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName)); } } private void Raise(string propertyName) { if (ErrorsChanged != null) ErrorsChanged(this, new System.ComponentModel.DataErrorsChangedEventArgs(propertyName)); } System.Collections.IEnumerable System.ComponentModel.INotifyDataErrorInfo.GetErrors(string propertyName) { System.Collections.Generic.List<object> propertyErrors; if (Errors.TryGetValue(propertyName, out propertyErrors)) return propertyErrors; return new System.Collections.Generic.List<object>(); } public void AddError(string propertyName, object error) { System.Collections.Generic.List<object> propertyErrors; if (!Errors.TryGetValue(propertyName, out propertyErrors)) { propertyErrors = new System.Collections.Generic.List<object>(); Errors.Add(propertyName, propertyErrors); } if (propertyErrors.Contains(error)) return; propertyErrors.Add(error); Raise(propertyName); } public void RemoveError(string propertyName) { Errors.Remove(propertyName); Raise(propertyName); } public virtual System.Collections.Generic.List<System.ComponentModel.DataAnnotations.ValidationResult> Validate(string propertyName) { var results = new System.Collections.Generic.List<System.ComponentModel.DataAnnotations.ValidationResult>(); var propertyInfo = GetType().GetProperty(propertyName); if (propertyInfo == null) return results; RemoveError(propertyName); var context = new System.ComponentModel.DataAnnotations.ValidationContext(this, null, null) { MemberName = propertyName }; if (!System.ComponentModel.DataAnnotations.Validator.TryValidateProperty(propertyInfo.GetValue(this, null), context, results)) { foreach (var validationResult in results) AddError(propertyName, validationResult.ErrorMessage); } return results; } } 
+4
source share
1 answer

I ran into this problem for a while and figured out that this is happening because the ValidationSummary control uses the name of the control to see if it has an error in its error collection. I worked on a solution and came up with the following behavior. As a result, we came across a different path due to some problems that I encountered, because a lot of the user interface is generated on the fly.

You can take a look and try to figure out if it can fix your problem:

 public class ValidationSummaryCountFixBehavior : Behavior<ValidationSummary> { private Dictionary<string, ValidationSummaryItem> _validationErrors = new Dictionary<string, ValidationSummaryItem>(); protected override void OnAttached() { base.OnAttached(); AssociatedObject.Loaded += new RoutedEventHandler(AssociatedObject_Loaded); } void AssociatedObject_Loaded(object sender, RoutedEventArgs e) { var target = AssociatedObject.Target as FrameworkElement ?? VisualTreeHelper.GetParent(AssociatedObject) as FrameworkElement; if (target != null) { target.BindingValidationError += new EventHandler<ValidationErrorEventArgs>(target_BindingValidationError); } AssociatedObject.Loaded -= new RoutedEventHandler(AssociatedObject_Loaded); } void target_BindingValidationError(object sender, ValidationErrorEventArgs e) { FrameworkElement inputControl = e.OriginalSource as FrameworkElement; if (((e != null) && (e.Error != null)) && ((e.Error.ErrorContent != null) && (inputControl != null))) { string message = e.Error.ErrorContent.ToString(); string goodkey = inputControl.GetHashCode().ToString(CultureInfo.InvariantCulture); goodkey = goodkey + message; if (e.Action == ValidationErrorEventAction.Added && ValidationSummary.GetShowErrorsInSummary(inputControl)) { string messageHeader = null; ValidationSummaryItem item = new ValidationSummaryItem(message, messageHeader, ValidationSummaryItemType.PropertyError, new ValidationSummaryItemSource(messageHeader, inputControl as Control), null); _validationErrors[goodkey] = item; } else { _validationErrors.Remove(goodkey); } } UpdateDisplayedErrors(); } private void UpdateDisplayedErrors() { AssociatedObject.Errors.Clear(); foreach (ValidationSummaryItem item in _validationErrors.Values) { AssociatedObject.Errors.Add(item); } } } 
+3
source

All Articles