Вы должны позволить вашей модели представления реализовать 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
.