Обзор:
У меня есть приложение Silverlight 4, где я вижу проблемное поведение из элемента управления ValidationSummary при использовании со ListBox, и я надеюсь, что кто-то может мне помочь.
На высоком уровне у меня есть ListBox, где каждая строка определяется (через ItemTemplate) с текстовым полем с именем "txtInput". Текстовое поле привязано (TwoWay) к свойству объекта, помеченному атрибутом DataAnnotation.Range. ListBox связан с коллекцией этих объектов. В том же родительском элементе управления у меня также есть элемент управления ValidationSummary.
Сценарий:
Представьте себе случай, когда в коллекции предметов есть два или более объектов. Пользователь увидит ListBox с несколькими строками, каждая из которых содержит текстовое поле. Если пользователь вводит недопустимые данные в первое текстовое поле, выдается исключение ValidationException, как ожидается, а элемент управления ValidationSummary отображает ошибку, как и ожидалось. Текстовое поле также получает стиль ошибки проверки (красная рамка).
Затем, если пользователь вводит недопустимые данные в текстовое поле второй строки (без фиксации данных в первом текстовом поле), второе текстовое поле также создает исключение ValidationException и получает стилевое сообщение об ошибке проверки (красная граница), как и ожидалось, HOWEVER элемент управления ValidationSummary показывает только один экземпляр сообщения об ошибке.
Затем, если пользователь исправляет одну (но не обе) из недопустимых текстовых записей, в фиксированном текстовом поле удаляется стиль проверки (красная граница), и поле ValidationSummary исчезает (что означает его думает, что все ошибки валидации были устранены и для .HasErrors установлено значение false). Во втором (все еще недействительном) текстовом поле по-прежнему применяется стилизация ошибки проверки (красная граница).
Я ожидаю, что оставшееся недействительное текстовое поле приведет к тому, что элемент управления ValidationSummary будет продолжать отображаться. Я предполагаю, что элемент управления ValidationSummary просто отслеживает сбои по имени свойства, и после успешной попытки установить свойство с таким именем он очищает маркер ошибки (то есть: он не учитывает случай, когда несколько экземпляров одноименные встречаются).
Требуется результат:
В конечном счете, я пытаюсь не дать пользователю нажать кнопку «Сохранить» на экране, когда есть недопустимые данные. В настоящее время я делаю это, привязывая свойство IsEnabled кнопки к свойству HasErrors объекта ValidationSummary, но это не работает, если ValidationSummary показывает вышеуказанное поведение.
Может кто-нибудь сказать мне, как можно заставить элемент управления ValidationSummary учитывать несколько сбоев одного и того же (повторяющегося) текстового поля, или предоставить жизнеспособный альтернативный способ отключить кнопку Сохранить, когда эти сбои существуют? (примечание: в моем реальном приложении каждая строка имеет несколько элементов управления вводом, поэтому любое решение должно учитывать это)
Фрагмент XAML:
<sdk:ValidationSummary x:Name="valSummary" />
<ListBox ItemsSource="{Binding DomainObjectCollection, Mode=TwoWay, ValidatesOnDataErrors=True, ValidatesOnNotifyDataErrors=True, ValidatesOnExceptions=true, NotifyOnValidationError=true}" >
<ListBox.ItemTemplate>
<DataTemplate>
<TextBox Name="txtInput" Text="{Binding DecimalValue, Mode=TwoWay, ValidatesOnDataErrors=True, ValidatesOnNotifyDataErrors=True, ValidatesOnExceptions=true, NotifyOnValidationError=true}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Button x:Name="btnSave" Content="Save" Command="{Binding SaveButtonCommand}" IsEnabled="{Binding HasErrors, ElementName=valSummary, Converter={StaticResource NotBoolConverter}}" />
Классы объектов домена:
[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;
}
}