I made a simple implementation of INotifyDataErrorInfo in a WPF 4.5 project. This is a new interface for WPF, but it has been available in Silverlight for some time.
I know that NET4.5 is still considered alpha, but I am trying to work if it is my code or framework that is to blame.
The interface works as expected, but fails when the object is bound to a DataGrid.
The exception that I get is:
System.NullReferenceException was not handled by user code
Message = The reference to the object is not installed in the object instance.
Source = PresentationFramework StackTrace: in MS.Internal.Data.ClrBindingWorker.OnDataErrorsChanged (INotifyDataErrorInfo indei, String propName) in MS.Internal.Data.PropertyPathWorker.OnErrorsChanged.` DeliverEvent (sender object, EventArgs e, type managerType) in System.Windows.WeakEventManager.DeliverEvent (object sender, EventArgs arguments) in System.ComponentModel.ErrorsChangedEventManager.OnErrorsChangedEventRotEfreTerEntrEntrEnterFeRentErEnterFeRentErEnterFeRentrEnterFeRentErFeRentEfreTerFeRentErEnterFeRentErEnterFeRentrEntrEnterFeRentErEnterFeRentErEnterFeRentErEnterItemErEnterFeRentErEnterItem ) in INotifyDataErrorInfoTest \ Person.cs: string 109 in INotifyDataErrorInfoTest.Person.AddErrorForProperty (String property, String error) in INotifyDataErrorInfoTest \ Person.cs : line 122 in INotifyDataErrorInfoTest.Person.Validate (String propertyName) in INotifyDataErrorInfoTest \ Person.cs: line 150 in INotifyDataErrorInfoTest.Person.set_FirstName (String value) in INotifyDataErrorInfoTest \ Person.cs:
The code is below or in the project at http://dl.dropbox.com/u/14740106/INotifyDataErrorInfoTest.zip
If the consensus is that this is a mistake, I will send an MS Connect message.
Testing: There are two text fields attached to one instance of the Person object. Set the first text box to James and he will not check the check and show a red frame. If you set any username in the grid for James, an exception will be thrown.
PS: I know that this is not MVVM, but simply to prove or refute the problem.
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>