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; } }