2015-05-04 5 views
2

Я использую интерфейс INotifyDataErrorInfo для реализации общего механизма валидации MVVM. Я реализующий интерфейс, вызвав OnValidate вместо OnPropertyChanged:INotifyDataErrorInfo и исключения привязки

public void OnValidate(dynamic value, [CallerMemberName] string propertyName = null) 
{ 
     if (PropertyChanged != null) 
       PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); 
     Validate(propertyName, value); 
} 

В методе Validate я генерации ошибки проверки, добавить их в словарь и поднять событие ErrorsChanged, если была обнаружена ошибка проверки или очищена :

if (entry.Validate(strValue, out errorNumber, out errorString) == false) 
{ 
     _validationErrors[propertyName] = new List<string> {errorString}; 
     RaiseErrorsChanged(propertyName); 
} 
else if (_validationErrors.ContainsKey(propertyName)) 
{ 
     _validationErrors.Remove(propertyName); 
     RaiseErrorsChanged(propertyName); 
} 

свойство HasErrors осуществляется смотря на ошибках словарь:

public bool HasErrors 
    { 
     get { return _validationErrors.Any(kv => kv.Value != null 
        && kv.Value.Count > 0); } 
    } 

Для предотвращения кнопки сохранения от б Eing включается при возникновении ошибки проверки - спасбросков команду canExecuteMethod смотрит на имущество HasErrors:

private bool IsSaveEnabled() 
{ 
    return HasErrors == false; 
} 

Все работает отлично, за исключением случая, когда у меня возникают обязательные ошибки - если переплетены значение (например) ап integer вводится не целое число - ошибка ErrorContent в текстовом поле обновляется с помощью строки ошибки: «Значение« что-то »не может быть преобразовано». Но механизм INotifyDataErrorInfo не обновляется об этом. HasErrors остается ложным, а Save включен, хотя в представлении есть ошибка. Я хотел бы найти способ размножить связывания исключение механизма INotifyDataErrorInfo, так что я смог бы:

  1. Отключить кнопку Сохранить (необходимо).
  2. Измените сообщение об ошибке проверки на более значимую строку ошибки (приятно иметь).

Я бы хотел найти общее решение MVVM без добавления кода в представлении.

Спасибо за помощь

ответ

0

Вот решение, которое я нашел.Это заставляет INotifyDataErrorInfo вести себя корректно в ViewModel (при наличии какой-либо ошибки проверки - HasError - истина) и позволяет добавлять ошибки проверки из viewModel. Помимо этого, он не требует изменений в представлении, изменениях в привязке или конвертерах.

Это решение включает в себя:

  • Добавление правила пользовательской проверки.
  • Добавление базового пользовательского элемента управления (из которого следует получить все представление).
  • Добавление кода в базу ViewModel.

Добавление правила пользовательского проверки - проверки объекта, который делает фактическую проверку и создает событие, когда изменения проверки:

class ValidationEntity : ValidationRule 
{ 
    public string Key { get; set; } 

    public string BaseName = "Base"; 

    public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo) 
    { 
     var fullPropertyName = BaseName + "." + Key; 
     ValidationEntry entry; 

     var validationResult = new ValidationResult(true, null); 

     if ((entry = ValidationManager.Instance.FindValidation(fullPropertyName)) != null) 
     { 
      int errorNumber; 
      string errorString; 

      var strValue = (value != null) ? value.ToString() : string.Empty; 

      if (entry.Validate(strValue, out errorNumber, out errorString) == false) 
      { 
       validationResult = new ValidationResult(false, errorString); 
      } 
     } 

     if (OnValidationChanged != null) 
     { 
      OnValidationChanged(Key, validationResult); 
     } 
     return validationResult; 
    } 

    public event Action<string, ValidationResult> OnValidationChanged; 
} 

Добавление пользовательского элемента управления базой, которая хранит список активных полей ввода, и добавляет правило проверки для каждого текстового поля связывания: Это код на управляющем пользователем базы:

private void OnLoaded(object sender, RoutedEventArgs routedEventArgs) 
{ 
    _textBoxes = FindAllTextBoxs(this); 

    var vm = DataContext as ViewModelBase; 
    if (vm != null) vm.UpdateAllValidationsEvent += OnUpdateAllValidationsEvent; 

    foreach (var textbox in _textBoxes) 
    { 
     var binding = BindingOperations.GetBinding(textbox, TextBox.TextProperty); 

     if (binding != null) 
     { 
      var property = binding.Path.Path; 
      var validationEntity = new ValidationEntity {Key = property}; 
      binding.ValidationRules.Add(validationEntity); 
      validationEntity.ValidationChanged += OnValidationChanged; 
     } 
    } 
} 
private List<TextBox> FindAllTextBoxs(DependencyObject fe) 
{ 
    return FindChildren<TextBox>(fe); 
} 

private List<T> FindChildren<T>(DependencyObject dependencyObject) 
      where T : DependencyObject 
{ 
    var items = new List<T>(); 

    if (dependencyObject is T) 
    { 
     items.Add(dependencyObject as T); 
     return items; 
    } 

    var count = VisualTreeHelper.GetChildrenCount(dependencyObject); 
    for (var i = 0; i < count; i++) 
    { 
     var child = VisualTreeHelper.GetChild(dependencyObject, i); 
     var children = FindChildren<T>(child); 
     items.AddRange(children); 
    } 
    return items; 
} 

Когда событие происходит ValidationChange - вид призван уведомление об ошибке проверки:

private void OnValidationChanged(string propertyName, ValidationResult validationResult) 
{ 
    var vm = DataContext as ViewModelBase; 

    if (vm != null) 
    { 
     if (validationResult.IsValid) 
     { 
      vm.ClearValidationErrorFromView(propertyName); 
     } 
     else 
     { 
      vm.AddValidationErrorFromView(propertyName, validationResult.ErrorContent as string); 
     } 
    } 
} 

База ViewModel держит два списка:

  • _notifyvalidationErrors, который используется в интерфейсе INotifyDataErrorInfo для отображения ошибок валидации.
  • _privateValidationErrors, который используется для отображения ошибок, генерируемых пользователем в правиле проверки.

При добавлении ошибки проверки с точки зрения - на _notifyvalidationErrors обновляется с пустым значением (только для обозначения есть ошибка проверки) строка ошибки не добавляется к _notifyvalidationErrors. Если мы добавим его туда, мы дважды получим строку ошибки проверки в текстовом поле ErrorContent. Строка ошибки проверки также добавляется в _privateValidationErrors (Потому что мы хотим, чтобы быть в состоянии держать его в ViewModel) Это код на базе ViewModel:

Реализация INotifyDataErrorInfo в представлении:

public bool HasErrors 
{ 
    get { return _notifyvalidationErrors.Any(kv => kv.Value != null); } 
} 
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged; 

public void RaiseErrorsChanged(string propertyName) 
{ 
     var handler = ErrorsChanged; 
     if (handler != null) 
      handler(this, new DataErrorsChangedEventArgs(propertyName)); 
} 
public IEnumerable GetErrors(string propertyName) 
{ 
    List<string> errorsForProperty; 
    _notifyvalidationErrors.TryGetValue(propertyName, out errorsForProperty); 

    return errorsForProperty; 
} 

Пользователь может добавить ошибки проверки из представления, вызвав методы ViewModelBase AddValidationError и ClearValidationError.

public void AddValidationError(string errorString, [CallerMemberName] string propertyName = null) 
{ 
    _notifyvalidationErrors[propertyName] = new List<string>{ errorString }; 
    RaiseErrorsChanged(propertyName); 
} 

public void ClearValidationError([CallerMemberName] string propertyName = null) 
{ 
    if (_notifyvalidationErrors.ContainsKey(propertyName)) 
    { 
     _notifyvalidationErrors.Remove(propertyName); 
     RaiseErrorsChanged(propertyName); 
    } 
} 

Представление может получить список всех текущих ошибок проверки из базы ViewModel, вызывая GetValidationErrors и методы GetValidationErrorsString.

public List<string> GetValidationErrors() 
{ 
    var errors = new List<string>(); 
    foreach (var key in _notifyvalidationErrors.Keys) 
    { 
     errors.AddRange(_notifyvalidationErrors[key]); 

     if (_privateValidationErrors.ContainsKey(key)) 
     { 
      errors.AddRange(_privateValidationErrors[key]); 
     } 
    } 
    return errors; 
} 

public string GetValidationErrorsString() 
{ 
    var errors = GetValidationErrors(); 
    var sb = new StringBuilder(); 
    foreach (var error in errors) 
    { 
     sb.Append("● "); 
     sb.AppendLine(error); 
    } 
    return sb.ToString(); 
} 
0

Set

ValidatesOnExceptions="True"

В вашем Binding выражении.

+0

Я пробовал: К сожалению, это не работает –

+0

Вы получаете исключение? Опубликовать SomeProperty и код, который выдает исключение. –

+0

Собственность: private int _someProperty; public int SomeProperty { get {return_someProperty; } { _someProperty = значение; OnValidate (значение); } } –

3

строкаINT случай не работает с MVVM, потому что ваш ViewModel не получает никакой информации из-за обязательного исключения.

Я вижу два пути, чтобы получить подтверждение вы хотите:

  1. Просто используйте строковые свойства в вашем ViewModel и когда вы должны пойти к вашей модели просто преобразовать строку типа модели.
  2. Создайте поведение или «специальные» элементы управления, чтобы вход в вашем представлении всегда был «конвертируемым» в ваш тип viewmodel.

Btw Я использую второй подход, потому что мне нужно :), но первый всегда будет работать и мне будет легче.

+0

Спасибо. К сожалению, я не могу использовать параметр 1, потому что я пытаюсь внедрить систему проверки для уже существующего кода с большим количеством свойств int и double. Я сейчас пытаюсь найти способ поймать ошибки привязки. –

+0

afaik, вы не можете сделать это с помощью mvvm и INotifyDataErrorInfo :(ваш viewmodel не получает никакой информации. Возьмите второй подход и создайте поведение или элементы управления, которые просто принимают правильный тип ввода. Что-то вроде http://stackoverflow.com/questions/16914224/ wpf-textbox-to-enter-decimal-values ​​ – blindmeis

+0

@TalSegal это правильный ответ. Может быть, вы должны обернуть свой «уже существующий код» слоем, который обрабатывает преобразования и проверки, реализуя IDEI, а не этот уже существующий код? реализуйте его как TypeDescriptor, который WPF Bindings будет использовать для взаимодействия с вашими типами, если он определен. – Will

Смежные вопросы