jmgardn2
jmgardn2

Reputation: 961

Get changed item from CollectionChanged event using TrulyObservableCollection

I'm using a TrulyObservableCollection as a datasource in a WPF DataGrid. My class implements the PropertyChange event properly (I get notification when a property changes). The CollectionChanged event gets triggered as well. However, my issue lies in the connection between the PropertyChanged event and CollectionChanged event. I can see in the PropertyChanged event which item is being changed (in this case the sender object), however I can't seem to find a way to see which one is changed from within the CollectionChanged event. The sender object is the whole collection. What's the best way to see which item has changed in the CollectionChanged event? The relevant code snippets are below. Thank you for your help, and let me know if there needs to be some clarification.

Code for setting up the collection:

    private void populateBret()
    {
        bretList = new TrulyObservableCollection<BestServiceLibrary.bretItem>(BestClass.BestService.getBretList().ToList());
        bretList.CollectionChanged += bretList_CollectionChanged;
        dgBretList.ItemsSource = bretList;
        dgBretList.Items.Refresh();
    }

    void bretList_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
    {
        //Do stuff here with the specific item that has changed
    }

Class that is used in the collection:

public class bretItem : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    private int _blID;
    public string _blGroup;

    [DataMember]
    public int blID
    {
        get { return _blID; }
        set
        {
            _blID = value;
            OnPropertyChanged("blID");
        }
    }

    [DataMember]
    public string blGroup
    {
        get { return _blGroup; }
        set
        {
            _blGroup = value;
            OnPropertyChanged("blGroup");
        }
    }

    protected void OnPropertyChanged (String name)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null)
        {
            handler(this, new PropertyChangedEventArgs(name));
        }
    }
}

TrulyObservableCollection class

public class TrulyObservableCollection<T> : ObservableCollection<T> where T : INotifyPropertyChanged
    {
        public TrulyObservableCollection()
            : base()
        {
            CollectionChanged += new NotifyCollectionChangedEventHandler(TrulyObservableCollection_CollectionChanged);
        }
        public TrulyObservableCollection(List<T> list)
            : base(list)
        {
            foreach (var item in list)
            {
                item.PropertyChanged += new PropertyChangedEventHandler(item_PropertyChanged);
            }
            CollectionChanged += new NotifyCollectionChangedEventHandler(TrulyObservableCollection_CollectionChanged);
        }

        void TrulyObservableCollection_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            if (e.NewItems != null)
            {
                foreach (Object item in e.NewItems)
                {
                    (item as INotifyPropertyChanged).PropertyChanged += new PropertyChangedEventHandler(item_PropertyChanged);
                }
            }
            if (e.OldItems != null)
            {
                foreach (Object item in e.OldItems)
                {
                    (item as INotifyPropertyChanged).PropertyChanged -= new PropertyChangedEventHandler(item_PropertyChanged);
                }
            }
        }

        void item_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            NotifyCollectionChangedEventArgs a = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);
            OnCollectionChanged(a);
        }
    }

EDIT:

In the item_PropertyChanged event the NotifyCollectionChangedEventArgs are set with NotifyCollectionChangedAction.Reset. This causes the OldItems and NewItems to be null, therefore I can't get the changed item in that case. I can't use .Add as the Datagrid is updated with an additional item. I can't appear to get .Replace to work either to get the changed item.

Upvotes: 1

Views: 10373

Answers (2)

Josh C.
Josh C.

Reputation: 4363

How about this:

In your ViewModel that contains the ObservableCollection of bretItem, the ViewModel subscribes to the CollectionChanged event of the ObservableCollection.

This will prevent the need of a new class TrulyObservableCollection derived from ObservableCollection that is coupled to the items within its collection.

Within the handler in your ViewModel, you can add and remove the PropertyChanged event handler as you are now. Since it is now your ViewModel that is being informed of the changes to objects within the collection, you can take the appropriate action.

public class BretListViewModel
{

    private void populateBret()
    {
        bretList = new ObservableCollection<BestServiceLibrary.bretItem>(BestClass.BestService.getBretList().ToList());
        bretList.CollectionChanged += bretList_CollectionChanged;              
    }

    void bretList_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
    {
         if (e.NewItems != null)
        {
            foreach (Object item in e.NewItems)
            {
                (item as INotifyPropertyChanged).PropertyChanged += new PropertyChangedEventHandler(item_PropertyChanged);
            }
        }
        if (e.OldItems != null)
        {
            foreach (Object item in e.OldItems)
            {
                (item as INotifyPropertyChanged).PropertyChanged -= new PropertyChangedEventHandler(item_PropertyChanged);
            }
        }
    }


    void item_PropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        var bret = sender as bretItem;

        //Update the database now!

        //One note:
        //The ObservableCollection raises its change event as each item changes.
        //You should consider a method of batching the changes (probably using an ICommand)
    }

}

A Thing of Note:

As an aside, it looks like you are breaking the MVVM pattern based upon this snippet:

dgBretList.ItemsSource = bretList;
dgBretList.Items.Refresh();

You probably should consider loading your ViewModel and binding your View to it instead of coding logic in the code-behind of your View.

Upvotes: 3

Phil Gan
Phil Gan

Reputation: 2863

It's not appropriate to use the collection changed event in this way because it's only meant to be fired when adding/removing items from the collection. Which is why you've hit a wall. You're also in danger of breaking the Liskov substitution principle with this approach.

It's probably better to implement the INotifyPropertyChanged interface on your collection class and fire that event when one of your items fires its property changed event.

Upvotes: 2

Related Questions