2016-01-07 3 views
10

я реализовал INotifyDataErrorInfo точно так, как описано в следующей ссылке:Force INotifyDataErrorInfo проверки

http://blog.micic.ch/net/easy-mvvm-example-with-inotifypropertychanged-and-inotifydataerrorinfo

У меня есть TextBox, который связан с строкового свойства в моей модели.

XAML

<TextBox Text="{Binding FullName, 
         ValidatesOnNotifyDataErrors=True, 
         NotifyOnValidationError=True, 
         UpdateSourceTrigger=PropertyChanged}" /> 

Модель

private string _fullName; 
public string FullName 
{ 
    get { return _fullName; } 
    set 
    { 
     // Set raises OnPropertyChanged 
     Set(ref _fullName, value); 

     if (string.IsNullOrWhiteSpace(_fullName)) 
      AddError(nameof(FullName), "Name required"); 
     else 
      RemoveError(nameof(FullName));     
    } 
} 

INotifyDataError Код

private Dictionary<string, List<string>> _errors = new Dictionary<string, List<string>>(); 

public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged; 

// get errors by property 
public IEnumerable GetErrors(string propertyName) 
{ 
    if (_errors.ContainsKey(propertyName)) 
     return _errors[propertyName]; 
    return null; 
} 

public bool HasErrors => _errors.Count > 0; 

// object is valid 
public bool IsValid => !HasErrors; 

public void AddError(string propertyName, string error) 
{ 
    // Add error to list 
    _errors[propertyName] = new List<string>() { error }; 
    NotifyErrorsChanged(propertyName); 
} 

public void RemoveError(string propertyName) 
{ 
    // remove error 
    if (_errors.ContainsKey(propertyName)) 
     _errors.Remove(propertyName); 
    NotifyErrorsChanged(propertyName); 
} 

public void NotifyErrorsChanged(string propertyName) 
{ 
    // Notify 
    if (ErrorsChanged != null) 
     ErrorsChanged(this, new DataErrorsChangedEventArgs(propertyName)); 
} 

Теперь все это работает фи ne, но он проверяется только после того, как I введите что-то в своем текстовом поле. Я бы хотел каким-то образом проверить по требованию, даже не касаясь текстового поля, скажем, нажатием кнопки.

Я попытался повысить PropertyChanged для всех моих свойств, как описано в this вопрос, но он не обнаруживает ошибки. Мне почему-то нужно, чтобы мой настройщик свойств вызывался, чтобы можно было обнаружить ошибки. Я ищу решение MVVM.

+0

Почему бы вам просто не вызвать метод NotifyErrorsChanged? Это вызовет событие ErrorsChanged, и все связанные элементы управления должны реагировать на него, если они имеют ValidatesOnNotifyDataErrors = True. – Stipo

+0

Я пробовал, он ничего не делает, и я предполагаю, что это потому, что словарь _errors пуст в то время. – kskyriacou

+0

Какова цель проверки по требованию? Ваша модель будет проверять себя сразу же после изменения любого из ее свойств. Ручная проверка просто даст тот же результат, потому что модель уже была проверена сама по себе. – TreeTree

ответ

1

Лучше всего использовать интерфейс команд реле. Посмотрите на это:

public class RelayCommand : ICommand 
{ 
    Action _TargetExecuteMethod; 
    Func<bool> _TargetCanExecuteMethod; 

    public RelayCommand(Action executeMethod) 
    { 
     _TargetExecuteMethod = executeMethod; 
    } 

    public RelayCommand(Action executeMethod, Func<bool> canExecuteMethod) 
    { 
     _TargetExecuteMethod = executeMethod; 
     _TargetCanExecuteMethod = canExecuteMethod; 
    } 

    public void RaiseCanExecuteChanged() 
    { 
     CanExecuteChanged(this, EventArgs.Empty); 
    } 
    #region ICommand Members 

    bool ICommand.CanExecute(object parameter) 
    { 
     if (_TargetCanExecuteMethod != null) 
     { 
      return _TargetCanExecuteMethod(); 
     } 
     if (_TargetExecuteMethod != null) 
     { 
      return true; 
     } 
     return false; 
    } 

    public event EventHandler CanExecuteChanged = delegate { }; 

    void ICommand.Execute(object parameter) 
    { 
     if (_TargetExecuteMethod != null) 
     { 
      _TargetExecuteMethod(); 
     } 
    } 
    #endregion 
} 

Вы бы объявить эту команду реле в вашей модели представления, как:

public RelayCommand SaveCommand { get; private set; } 

Теперь, в дополнение к зарегистрировав SaveCommand с OnSave и CanSave методы, так как вы расширяете от INotifyDataErrorInfo, вы можете подписаться на ErrorsChanged в конструкторе, а также:

public YourViewModel() 
{ 
    SaveCommand = new RelayCommand(OnSave, CanSave); 
    ErrorsChanged += RaiseCanExecuteChanged; 
} 

й вам нужны методы:

private void RaiseCanExecuteChanged(object sender, EventArgs e) 
{ 
     SaveCommand.RaiseCanExecuteChanged(); 
} 

public bool CanSave() 
{ 
    return !this.HasErrors; 
} 

private void OnSave() 
{ 
    //Your save logic here. 
} 

Кроме того, каждый раз после того, как вы звоните PropertyChanged, вы можете вызвать этот метод проверки:

private void ValidateProperty<T>(string propertyName, T value) 
    { 
     var results = new List<ValidationResult>(); 
     ValidationContext context = new ValidationContext(this); 
     context.MemberName = propertyName; 
     Validator.TryValidateProperty(value, context, results); 

     if (results.Any()) 
     { 
      _errors[propertyName] = results.Select(c => c.ErrorMessage).ToList(); 
     } 
     else 
     { 
      _errors.Remove(propertyName); 
     } 

     ErrorsChanged(this, new DataErrorsChangedEventArgs(propertyName)); 
    } 

С помощью этой установки, и если ваш ViewModel и простирается от INotifyPropertyChanged и INotifyDataErrorInfo (или из базового класса, который простирается от этих двух), когда вы привязываете кнопку к SaveCommand выше, среда WPF автоматически отключит ее, если есть ошибки проверки.

Надеюсь, это поможет.

+0

Я знаком с «RelayCommand», и я уже использую нечто подобное. Моя проверка выполняется на модели, поэтому моя модель просмотра не реализует 'INotifyDataErrorInfo'. Кроме того, проблема заключается в том, что моя проверка выполняется в настройщике свойств, который не вызывается, когда я хочу, поэтому я не вижу, как ваш ответ решает это. Я пойду с тем, как @ Grx70 упомянул в своем комментарии выше. – kskyriacou

10

Реализация INotifyDataErrorInfo, которую вы используете, несколько испорчена ИМХО. Он полагается на ошибки, хранящиеся в состоянии (списке), прикрепленном к объекту. Проблема с сохраненным состоянием, иногда, в движущемся мире, у вас нет возможности обновлять ее, когда захотите. Вот еще одна реализация MVVM, которая не полагается на сохраненное состояние, но вычисляет состояние ошибки «на лету».

Вещи обрабатываются немного по-другому, так как вам нужно поместить код проверки в центральный метод GetErrors (вы можете создать методы проверки подлинности для каждого объекта, вызванные этим центральным методом), а не в средствах настройки свойств.

public class ModelBase : INotifyPropertyChanged, INotifyDataErrorInfo 
{ 
    public event PropertyChangedEventHandler PropertyChanged; 
    public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged; 

    public bool HasErrors 
    { 
     get 
     { 
      return GetErrors(null).OfType<object>().Any(); 
     } 
    } 

    public virtual void ForceValidation() 
    { 
     OnPropertyChanged(null); 
    } 

    public virtual IEnumerable GetErrors([CallerMemberName] string propertyName = null) 
    { 
     return Enumerable.Empty<object>(); 
    } 

    protected void OnErrorsChanged([CallerMemberName] string propertyName = null) 
    { 
     OnErrorsChanged(this, new DataErrorsChangedEventArgs(propertyName)); 
    } 

    protected virtual void OnErrorsChanged(object sender, DataErrorsChangedEventArgs e) 
    { 
     var handler = ErrorsChanged; 
     if (handler != null) 
     { 
      handler(sender, e); 
     } 
    } 

    protected void OnPropertyChanged([CallerMemberName] string propertyName = null) 
    { 
     OnPropertyChanged(this, new PropertyChangedEventArgs(propertyName)); 
    } 

    protected virtual void OnPropertyChanged(object sender, PropertyChangedEventArgs e) 
    { 
     var handler = PropertyChanged; 
     if (handler != null) 
     { 
      handler(sender, e); 
     } 
    } 
} 

А вот два класса образца, которые демонстрируют, как использовать его:

public class Customer : ModelBase 
{ 
    private string _name; 

    public string Name 
    { 
     get 
     { 
      return _name; 
     } 
     set 
     { 
      if (_name != value) 
      { 
       _name = value; 
       OnPropertyChanged(); 
      } 
     } 
    } 

    public override IEnumerable GetErrors([CallerMemberName] string propertyName = null) 
    { 
     if (string.IsNullOrEmpty(propertyName) || propertyName == nameof(Name)) 
     { 
      if (string.IsNullOrWhiteSpace(_name)) 
       yield return "Name cannot be empty."; 
     } 
    } 
} 

public class CustomerWithAge : Customer 
{ 
    private int _age; 
    public int Age 
    { 
     get 
     { 
      return _age; 
     } 
     set 
     { 
      if (_age != value) 
      { 
       _age = value; 
       OnPropertyChanged(); 
      } 
     } 
    } 

    public override IEnumerable GetErrors([CallerMemberName] string propertyName = null) 
    { 
     foreach (var obj in base.GetErrors(propertyName)) 
     { 
      yield return obj; 
     } 

     if (string.IsNullOrEmpty(propertyName) || propertyName == nameof(Age)) 
     { 
      if (_age <= 0) 
       yield return "Age is invalid."; 
     } 
    } 
} 

Он работает как шарм с простым XAML, как это:

<TextBox Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}" /> 
<TextBox Text="{Binding Age, UpdateSourceTrigger=PropertyChanged}" /> 

(UpdateSourceTrigger является необязательным , если вы его не используете, он будет работать только тогда, когда фокус будет потерян).

С этим базовым классом MVVM вам не нужно принудительно выполнять какие-либо проверки. Но если вам это нужно, я добавил в ModelBase образец образца ForceValidation, который должен работать (я тестировал его, например, значение члена, такое как _name, которое было бы изменено без прохождения через публичный сеттер).

+0

Выполняя это, мы помещаем красные ящики ошибок во все мои контейнеры, например мои стековые панели в [этом примере] (http://i.imgur.com/uM4iCOg.png). Я использую проверку только в текстовом поле, которое даже не в этой панели стека. Кроме того, этот метод принудительно проверяет, как только будет создана модель. Я хотел бы проверить ** только **, когда свойство изменено пользователем ** или ** по требованию. – kskyriacou

+0

То, что вы просите, неясное и/или неполное. В вашем вопросе нет упоминания о стеке. Вы можете изменить стиль проверки так, как вы хотите, например: http://www.nbdtech.com/Blog/archive/2010/07/05/wpf-adorners-part-3-ndash-adorners-and-validation.aspx –

+0

Я не пытаюсь изменить стиль проверки, все, что я говорю, после использования кода все мои контейнеры теперь выделены красной линией. Пакетные панели были всего лишь одним примером. – kskyriacou