Пользовательский интерфейс не вызывает INotifyDataErrorInfo.getErrors()

#wpf #data-binding #mvvm #inotifydataerrorinfo

#wpf #привязка данных #mvvm #inotifydataerrorinfo

Вопрос:

У меня есть модель, реализующая оба INotifyPropertyChanged и INotifyDataErrorInfo . Событие изменения свойства срабатывает всякий раз, когда у меня изменяется свойство, но по какой-то причине, когда я вызываю обработчик события ошибки, пользовательский интерфейс когда-либо вызывает метод getErrors. Это приводит к тому, что ошибка проверки не отображается в пользовательском интерфейсе.

Может кто-нибудь взглянуть на то, как у меня настроен INotifyDataErrorInfo, и сказать мне, делаю ли я что-то не так?

Реализация базовой модели

 public class BaseChangeNotify : INotifyPropertyChanged, INotifyDataErrorInfo
{
    private bool isDirty;

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

    public BaseChangeNotify()
    {
    }

    public event PropertyChangedEventHandler PropertyChanged;

    public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;

    public bool IsDirty
    {
        get
        {
            return this.isDirty;
        }

        set
        {
            this.isDirty = value;
            this.OnPropertyChanged();
        }
    }

    public bool HasErrors
    {
        get
        {
            return this.errors.Count(e => e.GetType() == typeof(ErrorMessage)) > 0;
        }
    }

    public IEnumerable GetErrors(string propertyName)
    {
        if (string.IsNullOrEmpty(propertyName) ||
            !this.errors.ContainsKey(propertyName))
        {
            return null;
        }

        return this.errors[propertyName];/*.Where(e => (e is ErrorMessage));*/
    }

    protected virtual void AddError(string propertyName, string error, bool isWarning = false)
    {
        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);
            }

            this.OnErrorsChanged(propertyName);
        }
    }

    protected virtual void RemoveError(string propertyName, string error)
    {
        if (this.errors.ContainsKey(propertyName) amp;amp;
            this.errors[propertyName].Contains(error))
        {
            this.errors[propertyName].Remove(error);

            if (this.errors[propertyName].Count == 0)
            {
                this.errors.Remove(propertyName);
            }

            this.OnErrorsChanged(propertyName);
        }
    }

    public virtual void OnPropertyChanged([CallerMemberName] string propertyName = "")
    {
        // Perform the IsDirty check so we don't get stuck in a infinite loop.
        if (propertyName != "IsDirty")
        {
            this.IsDirty = true; // Each time a property value is changed, we set the dirty bool.
        }

        if (this.PropertyChanged != null)
        {
            // Invoke the event handlers attached by other objects.
            try
            {
                // When unit testing, this will always be null.
                if (Application.Current != null)
                {
                    try
                    {
                        Application.Current.Dispatcher.Invoke(() =>
                            this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName)));

                    }
                    catch (Exception)
                    {

                        throw;
                    }
                }
                else
                {
                    this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
                }
            }
            catch (Exception)
            {
                throw;
            }
        }
    }

    /// <summary>
    /// Called when an error has changed for this instance.
    /// </summary>
    /// <param name="propertyName">Name of the property.</param>
    public virtual void OnErrorsChanged([CallerMemberName] string propertyName = "")
    {
        if (string.IsNullOrWhiteSpace(propertyName))
        {
            return;
        }

        if (this.ErrorsChanged != null)
        {
            this.ErrorsChanged(this, new DataErrorsChangedEventArgs(propertyName));
        }
    }
}
  

Модель, использующая реализацию

 public class PayItem : BaseChangeNotify
{
    private Section section;

    public Section Section
    {
        get
        {
            return this.section;
        }

        set
        {
            this.section = value;
            this.ValidateSection();
            this.OnPropertyChanged();
        }
    }

    private void ValidateSection([CallerMemberName] string propertyName = "")
    {
        const string sectionError = "You must select a Section.";
        if (this.Section == null || this.Section.Name.Length > 1)
        {
            this.AddError(propertyName, sectionError);
        }
        else
        {
            this.RemoveError(propertyName, sectionError);
        }
    }
  

Представление пытается его использовать

 <ComboBox Name="SectionComboBox"
          ItemsSource="{Binding Path=ProjectSections}"
          SelectedItem="{Binding Path=SelectedPayItem.Section, 
                         NotifyOnValidationError=True,
                         UpdateSourceTrigger=PropertyChanged}">
  

Приложение пишется в WPF, а документов WPF довольно мало. Я прочитал документацию Silverlight по нему вместе с несколькими другими сообщениями в блоге, которые я нашел в Интернете, и реализовал каждый из различных способов, предложенных авторами блога. Каждый раз, когда результат один и тот же, GetErrors() метод никогда не попадает в механизм привязки.

Может ли кто-нибудь увидеть что-то, что я делаю неправильно? Когда у моей модели установлено свойство, я могу пройти через отладчик и в конечном итоге оказаться в OnErrorsChanged обработчике событий, и событие будет вызвано. Однако при его вызове ничего не происходит, поэтому я в тупике.

Заранее спасибо за любую помощь.

Джонатан

Редактировать

Также я хотел бы отметить, что последние пару месяцев я использовал IDataErrorInfo в базовом классе без каких-либо проблем. Привязка сработала, ошибки были отправлены в представление, и все было хорошо. Когда я сменил IDataErrorInfo на INotifyDataErrorInfo, проверка, похоже, прекратила связь с представлением.

Комментарии:

1. Есть NotifyOnDataErrors=true в ваших привязках?

2. Кажется, я не могу найти NotifyOnDataErrors ни в каких документах MSDN, ни в Intellisense. Однако я перепробовал все комбинации NotifyOnValidationError=True, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay, ValidatesOnNotifyDataErrors=True, ValidatesOnDataErrors=True настроек привязки к атрибуту элемента. Ни один из которых, похоже, не приводит к вызову getErrors().

3. msdn.microsoft.com/en-us/library /… да, ну что ж. Я бы использовал Snoop для следующей проверки ваших привязок.

Ответ №1:

INotifyDataErrorInfo.Свойство hasErrors должно возвращать значение true при возникновении события ErrorsChanged. В противном случае механизм привязки игнорирует ошибки. Ваше свойство hasErrors все время будет возвращать значение false. Это происходит потому, что вы проверяете элементы типа ErrorMessage, но ваш словарь содержит элементы типа KeyValuePair<строка, Список<строка>>. Кроме того, крайне неэффективно подсчитывать все элементы. Вы должны использовать .Вместо этого Any().

Кстати, документация MSDN по INotifyDataErrorInfo гласит следующее:

Обратите внимание, что механизм привязки никогда не использует свойство hasErrors, хотя вы можете использовать его в пользовательских отчетах об ошибках.

Это явно неправильно, и мне потребовались часы, чтобы выяснить это.

Комментарии:

1. Это имеет смысл. Я отошел от INotifyDataErrorInfo и использовал пользовательскую настройку проверки, которую я написал для Windows Phones в своем приложении WPF. Он работал с привязкой и разными типами сообщений почти таким же образом. Спасибо, что проследили за моей ошибкой, которая предотвратила возврат ошибок