ObservableDictionary for c#
Asked Answered
D

7

9

I'm trying to use following implementation of the ObservableDictionary: ObservableDictionary (C#).

When I'm using following code while binding the dictionary to a DataGrid:

ObserveableDictionary<string,string> dd=new ObserveableDictionary<string,string>();
....
dd["aa"]="bb";
....
dd["aa"]="cc";

at dd["aa"]="cc"; I'm getting following exception

Index was out of range. Must be non-negative and less than the size of the 
collection. Parameter name: index

This exception is thrown in CollectionChanged(this, new NotifyCollectionChangedEventArgs(action, newItem, oldItem) in the following method:

private void OnCollectionChanged(NotifyCollectionChangedAction action, KeyValuePair<TKey, TValue> newItem, KeyValuePair<TKey, TValue> oldItem)
{
  OnPropertyChanged();

  if (CollectionChanged != null) CollectionChanged(this, new NotifyCollectionChangedEventArgs(action, newItem, oldItem));
}

The index param seems to correspond to KeyValuePair<TKey, TValue> oldItem.

How can KeyValuePair<TKey, TValue> be out of range, and what should I do to make this work?

Deuno answered 16/5, 2012 at 10:20 Comment(1)
gist.github.com/kzu/cfe3cb6e4fe3efea6d24 this here seems pretty goodGlaive
A
7

Similar data structure, to bind to Dictionary type collection

http://drwpf.com/blog/2007/09/16/can-i-bind-my-itemscontrol-to-a-dictionary/

It provides a new Data structure ObservableDictionary and fires PropertyChanged in case of any change to underlying Dictionary.

Adulthood answered 16/5, 2012 at 10:31 Comment(0)
D
10

here's what I did in the end:

[Serializable]
public class ObservableKeyValuePair<TKey,TValue>:INotifyPropertyChanged
{
    #region properties
    private TKey key;
    private TValue value;

    public TKey Key
    {
        get { return key; }
        set
        {
            key = value;
            OnPropertyChanged("Key");
        }
    }

    public TValue Value
    {
        get { return value; }
        set
        {
            this.value = value;
            OnPropertyChanged("Value");
        }
    } 
    #endregion

    #region INotifyPropertyChanged Members

    [field:NonSerialized]
    public event PropertyChangedEventHandler PropertyChanged;

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

    #endregion
}

[Serializable]
public class ObservableDictionary<TKey,TValue>:ObservableCollection<ObservableKeyValuePair<TKey,TValue>>, IDictionary<TKey,TValue>
{

    #region IDictionary<TKey,TValue> Members

    public void Add(TKey key, TValue value)
    {
        if (ContainsKey(key))
        {
            throw new ArgumentException("The dictionary already contains the key");
        }
        base.Add(new ObservableKeyValuePair<TKey, TValue>() {Key = key, Value = value});
    }

    public bool ContainsKey(TKey key)
    {
        //var m=base.FirstOrDefault((i) => i.Key == key);
        var r = ThisAsCollection().FirstOrDefault((i) => Equals(key, i.Key));

        return !Equals(default(ObservableKeyValuePair<TKey, TValue>), r);
    }

    bool Equals<TKey>(TKey a, TKey b)
    {
        return EqualityComparer<TKey>.Default.Equals(a, b);
    }

    private ObservableCollection<ObservableKeyValuePair<TKey, TValue>> ThisAsCollection()
    {
        return this;
    }

    public ICollection<TKey> Keys
    {
        get { return (from i in ThisAsCollection() select i.Key).ToList(); }
    }

    public bool Remove(TKey key)
    {
        var remove = ThisAsCollection().Where(pair => Equals(key, pair.Key)).ToList();
        foreach (var pair in remove)
        {
            ThisAsCollection().Remove(pair);
        }
        return remove.Count > 0;
    }

    public bool TryGetValue(TKey key, out TValue value)
    {
        value = default(TValue);
        var r = GetKvpByTheKey(key);
        if (!Equals(r, default(ObservableKeyValuePair<TKey, TValue>)))
        {
            return false;
        }
        value = r.Value;
        return true;
    }

    private ObservableKeyValuePair<TKey, TValue> GetKvpByTheKey(TKey key)
    {
        return ThisAsCollection().FirstOrDefault((i) => i.Key.Equals(key));
    }

    public ICollection<TValue> Values
    {
        get { return (from i in ThisAsCollection() select i.Value).ToList(); }
    }

    public TValue this[TKey key]
    {
        get
        {
            TValue result;
            if (!TryGetValue(key,out result))
            {
                throw new ArgumentException("Key not found");
            }
            return result;
        }
        set
        {
            if (ContainsKey(key))
            {
                GetKvpByTheKey(key).Value = value;
            }
            else
            {
                Add(key, value);
            }
        }
    }

    #endregion

    #region ICollection<KeyValuePair<TKey,TValue>> Members

    public void Add(KeyValuePair<TKey, TValue> item)
    {
        Add(item.Key, item.Value);
    }

    public bool Contains(KeyValuePair<TKey, TValue> item)
    {
        var r = GetKvpByTheKey(item.Key);
        if (Equals(r, default(ObservableKeyValuePair<TKey, TValue>)))
        {
            return false;
        }
        return Equals(r.Value, item.Value);
    }

    public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
    {
        throw new NotImplementedException();
    }

    public bool IsReadOnly
    {
        get { return false; }
    }

    public bool Remove(KeyValuePair<TKey, TValue> item)
    {
        var r = GetKvpByTheKey(item.Key);
        if (Equals(r, default(ObservableKeyValuePair<TKey, TValue>)))
        {
            return false;
        }
        if (!Equals(r.Value,item.Value))
        {
            return false ;
        }
        return ThisAsCollection().Remove(r);
    }

    #endregion

    #region IEnumerable<KeyValuePair<TKey,TValue>> Members

    public new IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
    {
        return (from i in ThisAsCollection() select new KeyValuePair<TKey, TValue>(i.Key, i.Value)).ToList().GetEnumerator();
    }

    #endregion
}

This implementation looks and feels like dictionary to the user and like ObservableCollection to WPF

Deuno answered 17/5, 2012 at 10:3 Comment(3)
IMO its not good (for large data), because main purpose of dictionary is not to store, but to do fast retrieval using hashing techniques. this link has true ObservableDictionary with fast retrieval. (as ObservableDictionary wraps over Dictionary)Adulthood
Nice job! In TryGetValue, the if statement should be if (Equals(r, default(ObservableKeyValuePair<TKey, TValue>)))Calder
Nathan, you are right. It was giving me KeyNotFoundExceptionZeller
A
7

Similar data structure, to bind to Dictionary type collection

http://drwpf.com/blog/2007/09/16/can-i-bind-my-itemscontrol-to-a-dictionary/

It provides a new Data structure ObservableDictionary and fires PropertyChanged in case of any change to underlying Dictionary.

Adulthood answered 16/5, 2012 at 10:31 Comment(0)
H
2

I ended up writing a class to hold the Key-Value pair and using a collection of that class. I'm using Caliburn Micro which is where the BindableCollection comes from, but an ObservableCollection should work the same way. I use the MVVM pattern.

the viewmodel

using Caliburn.Micro;

private BindableCollection<KeyValuePair> _items;

public BindableCollection<KeyValuePair> Items
{
  get { return _items; }

  set
  {
    if (_items != value)
    {
      _items = value;
      NotifyOfPropertyChange(() => Items);
    }
  }
}

the custom keyValuePair

public class KeyValuePair 
{
  public string Key { get; set; }

  public string Value { get; set; }
}

and in the view

<ItemsControl ItemsSource="{Binding Items}">
  <ItemsControl.ItemTemplate>
    <DataTemplate>
      <Grid>
        <Grid.ColumnDefinitions>
          <ColumnDefinition Width="*" />
          <ColumnDefinition Width="2*" />
          <ColumnDefinition Width="Auto" />
        </Grid.ColumnDefinitions>

        <TextBox Grid.Column="0"
                 Text="{Binding Key, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
        <TextBox Grid.Column="1"
                 Text="{Binding Value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
      </Grid>
    </DataTemplate>
  </ItemsControl.ItemTemplate>
</ItemsControl>

It bothers me that I can't just bind to a dictionary, but I find this much easier and cleaner than writing an ObservableDictionary from scratch and worrying about the change notifications.

Hire answered 2/5, 2019 at 14:43 Comment(0)
R
1

ObservableDictionary was added to the .Net Framework at version 4.5:-

https://zamjad.wordpress.com/2012/10/12/observabledictionary-in-net-4-5/

Here is a link to the latest source code:-

https://referencesource.microsoft.com/#PresentationFramework/src/Framework/MS/Internal/Annotations/ObservableDictionary.cs

Reams answered 15/10, 2018 at 16:26 Comment(1)
Do you see it's internal ?Zosema
C
0

install Microsoft ParallelExtensionsExtras

Now available via Nuget: nuget.org/packages/MSFT.ParallelExtensionsExtras that library implements ObservableConcurrentDictionary, I tried and it works =]

Note that some of the features are now a part of newer .NET frameworks. Are the ParallelExtensions "Extras" still of value?

Microsoft Tour Blog: https://blogs.msdn.microsoft.com/pfxteam/2010/04/04/a-tour-of-parallelextensionsextras/

FYR, from .NET ObservableDictionary General Observable Dictionary Class for DataBinding/WPF C#

Consuetudinary answered 7/7, 2023 at 2:7 Comment(0)
A
-1

I first created a class called "ConcurrentObservableCollection" in which i extended the ObservableCollection functions.

public class ConcurrentObservableCollection<T> : ObservableCollection<T>
{
    private readonly object _lock = new object();

    public new void Add(T value)
    {
        lock (_lock)
        {
            base.Add(value);
        }
    }

    public List<T> ToList()
    {
        lock (_lock)
        {
            var copyList = new List<T>();
            copyList.AddRange(base.Items);
            return copyList;
        }
    }

    public new IEnumerator<T> GetEnumerator()
    {
        lock (_lock)
        {
            return base.GetEnumerator();
        }
    }

    public new bool Remove(T item)
    {
        lock (_lock)
        {
            return base.Remove(item);
        }
    }

    public new void Move(int oldIndex, int newIndex)
    {
        lock (_lock)
        {
            base.Move(oldIndex, newIndex);
        }
    }

    public new bool Contains(T item)
    {
        lock (_lock)
        {
            return base.Contains(item);
        }
    }

    public new void Insert(int index, T item)
    {
        lock (_lock)
        {
            base.Insert(index, item);
        }
    }

    public new int Count()
    {
        lock (_lock)
        {
            return base.Count;
        }
    }

    public new void Clear()
    {
        lock (_lock)
        {
            base.Clear();
        }
    }

    public new T this[int index]
    {
        get
        {
            lock (_lock)
            {
                return base[index];
            }
        }
    }
}

Then i replaced the exisitng "ObservabeCollection" with my new "ConcurrentObservableCollection"

Accommodative answered 19/8, 2022 at 10:23 Comment(0)
D
-2

Even I am using the ObservableDictionary of github, I also faced this exception. I had declared the dictionary variable at class level later I tried to create a new instance in the method where it was getting accessed.

OldCode which gave exception:

public class CName
{
  ObservableDictionary<string, string> _classVariableDictionary = new ObservableDictionary<string, string>();
}

NewCode which worked:

public void MethodName()
{
    ObservableDictionary<string, string> _localVariableDictionary = new ObservableDictionary<string, string>();
}
Dieterich answered 13/12, 2017 at 14:35 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.