Leszek
Leszek

Reputation: 71

Detecting if an ObservableCollection has been modified

I have a DataGrid nested in a DockPanel. The DockPanel serves as a data context:

DockPanel1.DataContext = GetData();

The GetData() method returns an ObservableCollection.

The ObservableCollection can be modified in the DataGrid as well as in a few textboxes nested in the DockPanel. I also navigate through the collection using a DataView.

I'd like to detect if the collection has been modified and warn a user when he/she tries to close the application without saving data.

Is there any built-in mechanism that I could use (a kind of "IsDirty" flag on the collection or on the view)? If not, I guess I will have to monitor all the controls and detect any changes manually.

Thanks, Leszek

Upvotes: 1

Views: 2642

Answers (2)

Cerebrate
Cerebrate

Reputation: 1391

To elaborate a bit on Clemens' answer above, here's the simple way to use these events (on the collection, and on the contained items) to implement an IsDirty flag such as you described:

public class DirtyCollection<T> : ObservableCollection<T> where T : INotifyPropertyChanged
{
    private bool isDirty = false;

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

    public void Clean()
    {
        this.isDirty = false;
    }

    protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
    {
        // We aren't concerned with how the collection changed, just that it did.
        this.isDirty = true;

        // But we do need to add the handlers to detect property changes on each item.
        switch (e.Action)
        {
            case NotifyCollectionChangedAction.Add:
                this.AddPropertyChanged(e.NewItems);
                break;

            case NotifyCollectionChangedAction.Remove:
                this.RemovePropertyChanged(e.OldItems);
                break;

            case NotifyCollectionChangedAction.Replace:
            case NotifyCollectionChangedAction.Reset:
                this.RemovePropertyChanged(e.OldItems);
                this.AddPropertyChanged(e.NewItems);
                break;
        }

        base.OnCollectionChanged(e);
    }

    private void AddPropertyChanged(IEnumerable items)
    {
        if (items != null)
        {
            foreach (var obj in items.OfType<INotifyPropertyChanged>())
            {
                obj.PropertyChanged += OnItemPropertyChanged;
            }
        }
    }

    private void RemovePropertyChanged(IEnumerable items)
    {
        if (items != null)
        {
            foreach (var obj in items.OfType<INotifyPropertyChanged>())
            {
                obj.PropertyChanged -= OnItemPropertyChanged;
            }
        }
    }

    private void OnItemPropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        // A property of a contained item has changed.
        this.isDirty = true;
    }
}

The code should be fairly self-explanatory.

You can, of course, remove the "where T : INotifyPropertyChanged" to allow objects that don't implement that interface to be stored in the collection, but then you won't be notified of any property changes on them, as without that interface, they can't notify you of them.

And should you want to keep track of not only that the collection is dirty but also how, some additions in OnCollectionChanged and OnItemPropertyChanged to record the information passed in the event args would do that nicely.

Upvotes: 1

Clemens
Clemens

Reputation: 128061

In order to detect changes in the collection itself, you would have to attach a CollectionChanged handler. If you also need to detect changes in the objects contained in the collection, you would have to attach PropertyChanged handlers to every object (provided that the objects implements INotifyPropertyChanged).

An implementation would basically look like this:

var collection = GetData();
collection.CollectionChanged += OnCollectionChanged;

...

private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
    switch (e.Action)
    {
        case NotifyCollectionChangedAction.Add:
            AddPropertyChanged(e.NewItems);
            break;
        case NotifyCollectionChangedAction.Remove:
            RemovePropertyChanged(e.OldItems);
            break;
        case NotifyCollectionChangedAction.Replace:
        case NotifyCollectionChangedAction.Reset:
            RemovePropertyChanged(e.OldItems);
            AddPropertyChanged(e.NewItems);
            break;
    }

    ...
}

private void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
{
    ...
}

private void AddPropertyChanged(IEnumerable items)
{
    if (items != null)
    {
        foreach (var obj in items.OfType<INotifyPropertyChanged>())
        {
            obj.PropertyChanged += OnPropertyChanged;
        }
    }
}

private void RemovePropertyChanged(IEnumerable items)
{
    if (items != null)
    {
        foreach (var obj in items.OfType<INotifyPropertyChanged>())
        {
            obj.PropertyChanged -= OnPropertyChanged;
        }
    }
}

Upvotes: 3

Related Questions