Вы должны позволить вашей модели представления реализовать INotifyDataErrorInfo
MSDN. Пример. Пример из MSDN (Silverlight). Начиная с .Net 4.5, это рекомендуемый способ ввести валидацию в ваши модели представления и поможет вам решить вашу проблему. При реализации этого интерфейса вам нужно будет предоставить свойство HasErrors
, к которому вы можете выполнить привязку. INotifyDataErrorInfo
заменяет устаревшее IDataErrorInfo
.
Привязка к Validation.HasError
напрямую, как вы это делали в триггерах, не будет работать, поскольку Validation.HasError
является присоединенным свойством только для чтения и поэтому не поддерживает привязку. Чтобы доказать это, я нашел это утверждение на MSDN:
... свойства зависимостей, доступные только для чтения, не подходят для многих сценариев, для которых свойства зависимостей обычно предлагают решение (а именно: привязка данных, непосредственно изменяемая по значению, проверка, анимация, наследование).
Как работает INotifyDataErrorInfo
Если для свойства ValidatesOnNotifyDataErrors
объекта Binding
установлено значение true
, механизм привязки будет искать реализацию INotifyDataErrorInfo
в источнике привязки, чтобы подписаться на событие ErrorsChanged
.
Если событие ErrorsChanged
возникает и HasErrors
оценивается как true
, привязка вызовет метод GetErrors()
для фактического свойства, чтобы получить конкретное сообщение об ошибке и применить настраиваемый шаблон ошибки проверки для визуализации ошибки. По умолчанию проверенный элемент обведен красной рамкой.
Как реализовать INotifyDataErrorInfo
Класс CustomVertex
на самом деле является ViewModel для DataGrid
столбцов, поскольку вы привязываетесь к его свойствам. Таким образом, он должен реализовать INotifyDataErrorInfo
. Это могло выглядеть так:
public class CustomVertex : INotifyPropertyChanged, INotifyDataErrorInfo
{
public CustomVertex()
{
this.errors = new Dictionary<string, List<string>>();
this.validationRules = new Dictionary<string, List<ValidationRule>>();
this.validationRules.Add(nameof(this.X), new List<ValidationRule>() {new DoubleValidationRule()});
this.validationRules.Add(nameof(this.Y), new List<ValidationRule>() {new DoubleValidationRule()});
}
public bool ValidateProperty(object value, [CallerMemberName] string propertyName = null)
{
lock (this.syncLock)
{
if (!this.validationRules.TryGetValue(propertyName, out List<ValidationRule> propertyValidationRules))
{
return;
}
// Clear previous errors from tested property
if (this.errors.ContainsKey(propertyName))
{
this.errors.Remove(propertyName);
OnErrorsChanged(propertyName);
}
propertyValidationRules.ForEach(
(validationRule) =>
{
ValidationResult result = validationRule.Validate(value, CultuteInfo.CurrentCulture);
if (!result.IsValid)
{
AddError(propertyName, result.ErrorContent, false);
}
}
}
}
// Adds the specified error to the errors collection if it is not
// already present, inserting it in the first position if isWarning is
// false. Raises the ErrorsChanged event if the collection changes.
public void AddError(string propertyName, string error, bool isWarning)
{
if (!this.errors.ContainsKey(propertyName))
{
this.errors[propertyName] = new List<string>();
}
if (!this.errors[propertyName].Contains(error))
{
if (isWarning)
{
this.errors[propertyName].Add(error);
}
else
{
this.errors[propertyName].Insert(0, error);
}
RaiseErrorsChanged(propertyName);
}
}
// Removes the specified error from the errors collection if it is
// present. Raises the ErrorsChanged event if the collection changes.
public void RemoveError(string propertyName, string error)
{
if (this.errors.ContainsKey(propertyName) &&
this.errors[propertyName].Contains(error))
{
this.errors[propertyName].Remove(error);
if (this.errors[propertyName].Count == 0)
{
this.errors.Remove(propertyName);
}
RaiseErrorsChanged(propertyName);
}
}
#region INotifyDataErrorInfo Members
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
public System.Collections.IEnumerable GetErrors(string propertyName)
{
if (String.IsNullOrEmpty(propertyName) ||
!this.errors.ContainsKey(propertyName)) return null;
return this.errors[propertyName];
}
public bool HasErrors
{
get { return errors.Count > 0; }
}
#endregion
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private double x;
public double X
{
get => x;
set
{
if (ValidateProperty(value))
{
this.x = value;
OnPropertyChanged();
}
}
}
private double y;
public double Y
{
get => this.y;
set
{
if (ValidateProperty(value))
{
this.y = value;
OnPropertyChanged();
}
}
}
private Dictionary<String, List<String>> errors;
// The ValidationRules for each property
private Dictionary<String, List<ValidationRule>> validationRules;
private object syncLock = new object();
}
Вид:
<DataGrid CanUserSortColumns="False" CanUserAddRows="True" ItemsSource="{Binding Vertices}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn x:Name="YColumn"
Width="*"
Header="Latitude"
Binding="{Binding Y, ValidatesOnNotifyDataErrors=True}"
Validation.ErrorTemplate="{DynamicResource ValidationErrorTemplate}" />
<DataGridTextColumn x:Name="XColumn"
Width="*"
Header="Longitude"
Binding="{Binding X, ValidatesOnNotifyDataErrors=True}"
Validation.ErrorTemplate="{DynamicResource ValidationErrorTemplate}" />
</DataGrid.Columns>
</DataGrid>
Ниже приведен шаблон ошибки проверки на тот случай, если вы хотите настроить визуальное представление (необязательно). Он устанавливается для проверенного элемента (в данном случае DataGridTextColumn
) через прикрепленное свойство Validation.ErrorTemplate
(см. Выше):
<ControlTemplate x:Key=ValidationErrorTemplate>
<StackPanel>
<!-- Placeholder for the DataGridTextColumn itself -->
<AdornedElementPlaceholder x:Name="textBox"/>
<ItemsControl ItemsSource="{Binding}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding ErrorContent}" Foreground="Red"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</ControlTemplate>
Кнопка, которая будет отключена при неудачной проверке (поскольку я не знаю, где находится эта кнопка в визуальном дереве, я предполагаю, что она разделяет DataContext
столбца DataGrid
, модель данных CustomVertex
):
<Button>
<Button.Style>
<Style TargetType="Button">
<Setter Property="IsEnabled" Value="True" />
<Style.Triggers>
<DataTrigger Binding="{Binding Path=HasErrors}" Value="True">
<Setter Property="IsEnabled" Value="False" />
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
В сети есть много примеров. Я обновил ссылки, чтобы начать с некоторого контента.
Я рекомендую переместить реализацию INotifyDataErrorInfo
в базовый класс вместе с INotifyPropertyChanged
и позволить всем вашим моделям представления наследовать его. Это делает логику проверки пригодной для повторного использования и поддерживает чистоту классов модели представления.
Вы можете изменить детали реализации INotifyDataErrorInfo
в соответствии с требованиями.
Примечания: Код не тестировался. Эти фрагменты должны работать, но предназначены для того, чтобы показать пример того, как INotifyDataErrorInfo
интерфейс может быть реализован.
03.01.2019
INotifyErrorDataInfo
в моей виртуальной машине или модели? Думаю, у меня возникли проблемы с тем, как это будет правильно реализовано в моей модели представления. 03.01.2019