Нарушен ли INotifyDataErrorInfo для WPF 4.5 DataGrids

#c# #wpf #.net-4.5 #inotifydataerrorinfo

#c# #wpf #.net-4.5 #inotifydataerrorinfo

Вопрос:

Я сделал простую реализацию INotifyDataErrorInfo в проекте WPF 4.5. Это новый интерфейс для WPF, но он уже некоторое время доступен в Silverlight.

Я знаю, что NET4.5 по-прежнему считается альфа-версией, но я пытаюсь понять, виноват ли в этом мой код или фреймворк.

Интерфейс работает, как и ожидалось, но терпит неудачу, когда объект привязан к сетке данных.

Исключение, которое я получаю, это:

Исключение System.NullReferenceException не было обработано пользовательским кодом
Message=Ссылка на объект не установлена для экземпляра объекта.
Источник=Трассировка стека PresentationFramework: в MS.Internal.Data.ClrBindingWorker.OnDataErrorsChanged(INotifyDataErrorInfo indei, строка propName) в MS.Internal.Data.PropertyPathWorker.OnErrorsChanged(отправитель объекта, DataErrorsChangedEventArgs e) в системе.Windows.WeakEventManager.Список прослушиваний `1.DeliverEvent(отправитель объекта, EventArgs e, Тип managerType) в системе.Windows.WeakEventManager.DeliverEvent(отправитель объекта, аргументы EventArgs) в системе.ComponentModel.Ошибка Changedeventmanager.OnErrorsChanged(отправитель объекта, аргументы DataErrorsChangedEventArgs) в INotifyDataErrorInfoTest.Person.NotifyErrorsChanged(строковое свойство) в INotifyDataErrorInfoTestPerson.cs: строка 109 в INotifyDataErrorInfoTest.Person.AddErrorForProperty(строковое свойство, ошибка строки) в INotifyDataErrorInfoTestPerson.cs: строка 122 в INotifyDataErrorInfoTest.Person.Проверка (строковое свойство) в INotifyDataErrorInfoTestPerson.cs:строка 150 в INotifyDataErrorInfoTest.Person.set_FirstName(строковое значение) в INotifyDataErrorInfoTestPerson.cs: строка 18

Код приведен ниже или в проекте по адресу http://dl.dropbox.com/u/14740106/INotifyDataErrorInfoTest.zip

Если все согласны с тем, что это ошибка, я отправлю сообщение в MS Connect.

Тестирование: есть два текстовых поля, привязанных к одному экземпляру объекта Person. Установите для первого текстового поля значение James, и оно не пройдет проверку и покажет красное поле. Если вы установите для имени любого пользователя в сетке значение James, будет выдано исключение.

PS: Я знаю, что это не MVVM, но это просто для доказательства или опровержения проблемы.

  public class Person : INotifyDataErrorInfo, INotifyPropertyChanged
    {
        string _firstName;
        public string FirstName
        {
            get { return _firstName; }
            set
            {
                _firstName = value;
                Validate("FirstName");
                OnPropertyChanged("FirstName");
            }
        }

        string _lastName;
        public string LastName
        {
            get { return _lastName; }
            set
            {
                _lastName = value;
                Validate("LastName");
                OnPropertyChanged("LastName");
            }
        }

        public Person()
        {
        }

        public Person(string first, string last)
        {
            this._firstName = first;
            this._lastName = last;
        }

        #region INotifyPropertyChanged Members

        /// <summary>
        /// Event to indicate that a property has changed.
        /// </summary>
        [field: NonSerialized]
        public event PropertyChangedEventHandler PropertyChanged;

        /// <summary>
        /// Called when a property is changed
        /// </summary>
        /// <param name="propertyName">The name of the property that has changed.</param>
        protected virtual void OnPropertyChanged(string propertyName)
        {
            OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
        }

        /// <summary>
        /// Called when a property is changed
        /// </summary>
        /// <param name="e">PropertyChangedEventArgs</param>
        protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
        {

            //Validate the property
            Validate(e.PropertyName);

            if (null != PropertyChanged)
            {
                PropertyChanged(this, e);
            }

        }

        #endregion

        #region INotifyDataErrorInfo Members

        public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
        private Dictionary<string, List<string>> _errors = new Dictionary<string, List<string>>();

        public IEnumerable GetErrors(string propertyName)
        {
            if (string.IsNullOrEmpty(propertyName))
            {
                return (_errors.Values);
            }

            MakeOrCreatePropertyErrorList(propertyName);
            return _errors[propertyName];
        }

        public bool HasErrors
        {
            get
            {
                return (_errors.Where(c => c.Value.Count > 0).Count() > 0);
            }
        }

        void NotifyErrorsChanged(string property)
        {
            if (ErrorsChanged != null)
            {
                ErrorsChanged(this, new DataErrorsChangedEventArgs(property));
            }
        }
        public void ClearErrorFromProperty(string property)
        {
            MakeOrCreatePropertyErrorList(property);
            _errors[property].Clear();
            NotifyErrorsChanged(property);
        }
        public void AddErrorForProperty(string property, string error)
        {
            MakeOrCreatePropertyErrorList(property);
            _errors[property].Add(error);
            NotifyErrorsChanged(property);
        }

        void MakeOrCreatePropertyErrorList(string propertyName)
        {
            if (!_errors.ContainsKey(propertyName))
            {
                _errors[propertyName] = new List<string>();
            }
        }

        #endregion

        /// <summary>
        /// Force the object to validate itself using the assigned business rules.
        /// </summary>
        /// <param name="propertyName">Name of the property to validate.</param>
        public void Validate(string propertyName)
        {
            if (string.IsNullOrEmpty(propertyName))
            {
                return;
            }

            if (propertyName == "FirstName")
            {
                if (FirstName == "James")
                {
                    AddErrorForProperty(propertyName, "FirstName can't be James");
                }
                else
                {
                    ClearErrorFromProperty(propertyName);
                }
            }
        }
    }

public class NameList : ObservableCollection<Person>
    {
        public NameList()
            : base()
        {
            Add(new Person("Willa", "Cather"));
            Add(new Person("Isak", "Dinesen"));
            Add(new Person("Victor", "Hugo"));
            Add(new Person("Jules", "Verne"));
        }
    }

   public partial class MainWindow : Window
    {

        Person _person = new Person();

        public MainWindow()
        {
            InitializeComponent();
            DataContext = this;
        }

        public Person Person
        {
            get { return _person; }
        }
}

<Window x:Class="INotifyDataErrorInfoTest.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:c="clr-namespace:INotifyDataErrorInfoTest"
        Title="MainWindow" Height="350" Width="525">

    <Window.Resources>
        <c:NameList x:Key="NameListData"/>
    </Window.Resources>
    <StackPanel>
        <StackPanel.Resources>
            <Style TargetType="TextBox">
                <Setter Property="Margin" Value="5"></Setter>
            </Style>
        </StackPanel.Resources>
        <TextBox Text="{Binding Person.FirstName, Mode=TwoWay, ValidatesOnNotifyDataErrors=True}"/>
        <TextBox Text="{Binding Person.LastName, Mode=TwoWay, ValidatesOnNotifyDataErrors=True}"/>
        <TextBlock>To generate an error, set the FirstName of any row to James.
        </TextBlock>
        <DataGrid ItemsSource="{Binding Source={StaticResource NameListData}}" AutoGenerateColumns="True"></DataGrid>
    </StackPanel>
</Window>
 

Ответ №1:

Ответ — ДА. Я открыл заявку в Microsoft, и они подтвердили, что код в порядке, и это ошибка с сеткой данных .NET 4.5.

Это наша ошибка, а не ваша. Когда вы редактируете ячейку DataGrid, DataGrid удаляет привязки для шаблона «отображение» и заменяет их привязками для шаблона «редактирование». Отброшенные привязки должны прекратить прослушивание события INDEI.ErrorsChanged. Они этого не делают (это ошибка), но они больше не готовы получать событие. При поступлении события происходит сбой с нулевой ссылкой.

Это будет исправлено в финальной версии. Спасибо, что нашли и сообщили об этом.

Довольно большая ошибка, чтобы ждать до финальной версии. Будем надеяться, что это будет исправлено к следующему выпуску.

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

1. у вас есть ссылка на ошибку, чтобы я мог ее отследить?