none
Observable Dictionary, problem with Remove...

    Question

  • Im having trouble getting an observable dictionary wrapper class to work. The add part of it works fine, my problem is at the remove method.

     

    My class looks something like this:

     

    public class ThreadSafeDictionary<TKey, TValue> : INotifyCollectionChanged, INotifyPropertyChanged

    {

    private Dictionary<TKey, TValue> _Dictionary;

     

    public void Add(TKey key, TValue value)

    {

    _Dictionary.Add(key, value);

    OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, new KeyValuePair<TKey, TValue>(key, value)));

     

    OnPropertyChanged("Keys");

    OnPropertyChanged("Values");

    OnPropertyChanged("Count");

    }

     

    public void Remove(TKey key)

    {

    _Dictionary.Remove(key);

     

    //PROBLEM HERE! Which NotifyCollectionChangedEventArgs to use??

     

    OnPropertyChanged("Keys");

    OnPropertyChanged("Values");

    OnPropertyChanged("Count");

    }

    }

     

    I've tried all combinations of NotifyCollectionChangedEventArgs construcors, but they all seem to give me an exception, and the overload that looks like its the correct one for the Remove event seems to want an integer when all I have is a TKey?

     

    Any help appreciated!

    Monday, September 03, 2007 3:03 AM

Answers

  •  

    To truly be a dictionary and truly be observable, you must implement the following interfaces:

     

    [Serializable]

    public class ObservableDictionary< TKey, TValue > :

        IDictionary< TKey, TValue >,

        ICollection< KeyValuePair< TKey, TValue > >,

        IEnumerable< KeyValuePair< TKey, TValue > >,

        IDictionary,

        ICollection,

        IEnumerable,

        ISerializable,

        IDeserializationCallback,

        INotifyCollectionChanged,

        INotifyPropertyChanged

     

    None of these interfaces are indexed by integer, but unfortunately, observable collections are.

     

    In your example, you are trying to create an observable collection by aggregating a Dictionary.  This won't work because none of the public interfaces expose the index of entries within the dictionary's internal collection.

     

    Dictionaries basically work by aggregating an internal collection of DictionaryEntry values.  This collection is usually of type KeyedCollection< TKey, DictionaryEntry >When you fire your collection change notification for the Remove action, you need to supply both the DictionaryEntry value and the index of that value within the aggregate collection.

     

    So rather than aggregate a Dictionary, you will need to aggregate a KeyedCollection, just as the standard Dictionary template class does.  Unfortunately, this means you must also implement all of the interfaces listed above, which involves a lot of grunt work.

     

    You are already firing the correct property changes in your Remove() function.  Your collection change code should look something like this:

     

    Code Snippet

     

    DictionaryEntry entry = _aggregateCollection[key];

    int index = _aggregateCollection.IndexOf(key);

    if (index > -1)

    {

        OnCollectionChanged(new NotifyCollectionChangedEventArgs(

            NotifyCollectionChangedAction.Remove, entry, index));

    }

    else

    {

        OnCollectionChanged(new NotifyCollectionChangedEventArgs(

            NotifyCollectionChangedAction.Reset));

    }

     

     

     

    Monday, September 03, 2007 5:50 AM
  • If the collection is bound to an items source and you issue a Remove action with an index of -1, you will cause an exception.  The index must be greater than or equal to 0 and less than the collection's count.

     

    Note that collection change notifications exist as an optimization for keeping collection views in sync with databound collections.  The code that monitors these events expects to map the supplied index to an item in the view.  Although you could issue a Reset action when a single item is removed, this defeats the purpose and would cause the entire view to be regenerated.

    Monday, September 03, 2007 7:36 AM

All replies

  • Hello, Did you have tried this way?

     

    Code Snippet

    public void Remove(TKey key)
     {
                TValue value = _Dictionary[key];
                _Dictionary.Remove(key);

                OnCollectionChanged(new NotifyCollectionChangedEventArgs(

    NotifyCollectionChangedAction.Remove,

    new KeyValuePair<TKey, TValue>(key, value)));

                ...
    }

     

     

    Also, this thread may helpful for you.

     

    Monday, September 03, 2007 5:08 AM
  • Thanks for the reply Yiling, I tried as you suggested and I get this error on my event invocation:

     

    Collection Remove event must specify item position.

     

    My code in that section looks like this:

     

    Code Snippet

    public event NotifyCollectionChangedEventHandler CollectionChanged;

    protected void OnCollectionChanged(NotifyCollectionChangedEventArgs e)

    {

    NotifyCollectionChangedEventHandler handler = CollectionChanged;

     

    if (handler != null)

    handler(this, e);

    }

     

     

    Monday, September 03, 2007 5:48 AM
  •  

    To truly be a dictionary and truly be observable, you must implement the following interfaces:

     

    [Serializable]

    public class ObservableDictionary< TKey, TValue > :

        IDictionary< TKey, TValue >,

        ICollection< KeyValuePair< TKey, TValue > >,

        IEnumerable< KeyValuePair< TKey, TValue > >,

        IDictionary,

        ICollection,

        IEnumerable,

        ISerializable,

        IDeserializationCallback,

        INotifyCollectionChanged,

        INotifyPropertyChanged

     

    None of these interfaces are indexed by integer, but unfortunately, observable collections are.

     

    In your example, you are trying to create an observable collection by aggregating a Dictionary.  This won't work because none of the public interfaces expose the index of entries within the dictionary's internal collection.

     

    Dictionaries basically work by aggregating an internal collection of DictionaryEntry values.  This collection is usually of type KeyedCollection< TKey, DictionaryEntry >When you fire your collection change notification for the Remove action, you need to supply both the DictionaryEntry value and the index of that value within the aggregate collection.

     

    So rather than aggregate a Dictionary, you will need to aggregate a KeyedCollection, just as the standard Dictionary template class does.  Unfortunately, this means you must also implement all of the interfaces listed above, which involves a lot of grunt work.

     

    You are already firing the correct property changes in your Remove() function.  Your collection change code should look something like this:

     

    Code Snippet

     

    DictionaryEntry entry = _aggregateCollection[key];

    int index = _aggregateCollection.IndexOf(key);

    if (index > -1)

    {

        OnCollectionChanged(new NotifyCollectionChangedEventArgs(

            NotifyCollectionChangedAction.Remove, entry, index));

    }

    else

    {

        OnCollectionChanged(new NotifyCollectionChangedEventArgs(

            NotifyCollectionChangedAction.Reset));

    }

     

     

     

    Monday, September 03, 2007 5:50 AM
  • For remove event, you can try this:

     

    OnCollectionChanged(new NotifyCollectionChangedEventArgs(

    NotifyCollectionChangedAction.Remove,

    new KeyValuePair<TKey, TValue>(key, value), -1));

     

    Monday, September 03, 2007 6:36 AM
  • If the collection is bound to an items source and you issue a Remove action with an index of -1, you will cause an exception.  The index must be greater than or equal to 0 and less than the collection's count.

     

    Note that collection change notifications exist as an optimization for keeping collection views in sync with databound collections.  The code that monitors these events expects to map the supplied index to an item in the view.  Although you could issue a Reset action when a single item is removed, this defeats the purpose and would cause the entire view to be regenerated.

    Monday, September 03, 2007 7:36 AM
  • I've posted an Observable Dictionary Sample for anyone who might be interested.

    Monday, September 17, 2007 3:41 AM
  • I am having the exact same problem. I have implemented a left leaning red black tree an want it to be observable so I can use it for visualization of data. At the moment I am visualizing itself by using wpf and bind to the tree and nodes. Everything works except that I have to use Reset which seems to slow it down significantly(even for moderately small tree's of around 250 elements).

    Of course with a red black tree  there generally is a large number of nodes being changes for each insertion or removal because of balancing.  But since I generally will not visualize the tree itself so the internal structure of the tree won't matter.

     

    The real issue is then on removing elements from the tree. Because an index is required and there is no real index for an element(and if there were it could change on a rebalance) I can't fire a collection changed remove event.

     

    It seems my only option is to create a custom container?

     

    Also, if the internal visual collection uses an linear collection then it defeats the purpose using a tree for optimization? I may get O(log(N)) operations from my tree but if the thing observing the collection maps the elements to a linear collection then whats the point as they will be O(N).

    Saturday, June 12, 2010 7:42 PM