.NET ObservableDictionary
I'd suggest that you implement the IDictionary<TKey, TValue>
instead of inheriting from Dictionary<TKey, TValue>
. Since you have to use new
rather than override
it's possible that the methods are simply being called on the base class rather than your class. I'd be tempted to use a Dictionary<TKey, TValue>
internally to do the actual storing of data.
In fact I found this:
http://blogs.microsoft.co.il/blogs/shimmy/archive/2010/12/26/observabledictionary-lt-tkey-tvalue-gt-c.aspx
ObservableDictionary not properly implementating INotifyCollectionChanged
After reproducing this issue and doing my own tests, I've found the problem.
Looking at the call stack, the exception is actually coming from CollectionView
, which is what ItemsControl
uses internally to monitor its ItemsSource
. This is why the exception only occures when the ObservableDictionary
is bound to.
I compared the implementation of ObservableDictionary
above to the .NET source code for ObservableCollection
. This link takes you to the method in the source that gets called when an item is replaced. You'll notice that they use an overload of the NotifyCollectionChangedEventArgs
constructor which includes index
. But the ObservableDictionary
implementation uses a different constructor which does not.
Switching the constructor to the one being used by ObservableCollection
prevents this exception.
So, insead of:
private void OnCollectionChanged(NotifyCollectionChangedAction action, KeyValuePair<TKey, TValue> newItem, KeyValuePair<TKey, TValue> oldItem)
{
OnPropertyChanged();
if (CollectionChanged != null) CollectionChanged(this, new NotifyCollectionChangedEventArgs(action, newItem, oldItem));
}
Use this:
private void OnCollectionChanged(NotifyCollectionChangedAction action, KeyValuePair<TKey, TValue> newItem, KeyValuePair<TKey, TValue> oldItem)
{
OnPropertyChanged();
if (CollectionChanged != null) CollectionChanged(this, new NotifyCollectionChangedEventArgs(action, newItem, oldItem, Dictionary.ToList().IndexOf(newItem)));
}
Remove()
was throwing Collection Remove event must specify item position.
exception because the called overload of CollectionChanged
was also missing an index parameter.
public bool Remove(TKey key)
{
if (key == null) throw new ArgumentNullException("key");
TValue value;
if (!Dictionary.TryGetValue(key, out value)) { return false; }
var removeditem = new KeyValuePair<TKey, TValue>(key, value);
var removedindex = Dictionary.ToList().IndexOf(removeditem);
var removed = Dictionary.Remove(key);
if (removed)
OnCollectionChanged(NotifyCollectionChangedAction.Remove, removeditem, removedindex);
return removed;
}
And the new overload called in this case:
private void OnCollectionChanged(NotifyCollectionChangedAction action, KeyValuePair<TKey, TValue> changedItem, int removedindex)
{
OnPropertyChanged();
if (CollectionChanged != null) CollectionChanged(this, new NotifyCollectionChangedEventArgs(action, changedItem, removedindex));
}
I've left a note on the GitHub page for the original code (the one you linked to) informing them of this error so they can fix it- or at least so that future users are aware of it.
ObservableDictionary for c#
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.
General Observable Dictionary Class for DataBinding/WPF C#
If you really want to make an ObservableDictionary
, I'd suggest creating a class that implements both IDictionary
and INotifyCollectionChanged
. You can always use a Dictionary
internally to implement the methods of IDictionary
so that you won't have to reimplement that yourself.
Since you have full knowledge of when the internal Dictionary
changes, you can use that knowledge to implement INotifyCollectionChanged
.
Alternative to ObservableDictionary that focuses on performance
It's very simple to write your own that does not cause a rebinding each time an add / update or delete is done. We wrote our own because of this very reason. Essentially what we do is to disable the notification that a change has been made until all objects in the collection have been processed, and then we generate the notification. It looks something like this. As you can see, we use MvvmLight as our MVVM framework library. This will improve performance tremendously.
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Linq;
using FS.Mes.Client.Mvvm.MvvmTools.MvvmLight;
namespace FS.Mes.Client.Mvvm.MvvmTools
{
/// <summary>
/// Our implementation of ObservableCollection. This fixes one significant limitation in the .NET Framework implementation. When items
/// are added or removed from an observable collection, the OnCollectionChanged event is fired for each item. Depending on how the collection
/// is bound, this can cause significant performance issues. This implementation gets around this by suppressing the notification until all
/// items are added or removed. This implementation is also thread safe. Operations against this collection are always done on the thread that
/// owns the collection.
/// </summary>
/// <typeparam name="T">Collection type</typeparam>
public class FsObservableCollection<T> : ObservableCollection<T>
{
#region [Constructor]
/// <summary>
/// Constructor
/// </summary>
public FsObservableCollection()
{
DispatcherHelper.Initialize();
}
#endregion
#region [Public Properties]
/// <summary>
/// Gets or sets a property that determines if we are delaying notifications on updates.
/// </summary>
public bool DelayOnCollectionChangedNotification { get; set; }
#endregion
/// <summary>
/// Add a range of IEnumerable items to the observable collection and optionally delay notification until the operation is complete.
/// </summary>
/// <param name="items"></param>
/// <param name="delayCollectionChangedNotification">Value indicating whether delay notification will be turned on/off</param>
public void AddRange(IEnumerable<T> items, bool delayCollectionChangedNotification = true)
{
if (items == null)
throw new ArgumentNullException("items");
DoDispatchedAction(() =>
{
DelayOnCollectionChangedNotification = delayCollectionChangedNotification;
// Do we have any items to add?
if (items.Any())
{
try
{
foreach (var item in items)
this.Add(item);
}
finally
{
// We're done. Turn delay notification off and call the OnCollectionChanged() method and tell it we had a 'dramatic' change
// in the collection.
DelayOnCollectionChangedNotification = false;
this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
}
});
}
/// <summary>
/// Clear the items in the ObservableCollection and optionally delay notification until the operation is complete.
/// </summary>
public void ClearItems(bool delayCollectionChangedNotification = true)
{
// Do we have anything to remove?
if (!this.Any())
return;
DoDispatchedAction(() =>
{
try
{
DelayOnCollectionChangedNotification = delayCollectionChangedNotification;
this.Clear();
}
finally
{
// We're done. Turn delay notification off and call the OnCollectionChanged() method and tell it we had a 'dramatic' change
// in the collection.
DelayOnCollectionChangedNotification = false;
this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
});
}
/// <summary>
/// Override the virtual OnCollectionChanged() method on the ObservableCollection class.
/// </summary>
/// <param name="e">Event arguments</param>
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
DoDispatchedAction(() =>
{
if (!DelayOnCollectionChangedNotification)
base.OnCollectionChanged(e);
});
}
/// <summary>
/// Makes sure 'action' is executed on the thread that owns the object. Otherwise, things will go boom.
/// </summary>
///<param name="action">The action which should be executed</param>
private static void DoDispatchedAction(Action action)
{
DispatcherHelper.CheckInvokeOnUI(action);
}
}
}
How to reference a struct's type parameters in a generic C# class?
Well, you have 2
generic parameters, let them be TKey
and TValue
:
public class MyObservableDictionary<TKey, TValue>
Your class implements two interfaces:
- "I want the ... collection to be a dictionary" -
IDictionary<TKey, TValue>
- "I would like to create my own implementation of
INotifyCollectionChanged
"
Add them:
public class MyObservableDictionary<TKey, TValue>
: IDictionary<TKey, TValue>,
INotifyCollectionChanged
Finally, if I've understood you right, you want to restrict both TKey
and TValue
to be struct
only; you can do it with a help of where
:
public class MyObservableDictionary<TKey, TValue>
: IDictionary<TKey, TValue>,
INotifyCollectionChanged
where TKey : struct
where TValue : struct {
//TODO: implementation here
}
Can't serialize my `ObservableDictionaryTKey,TValue` class
This has less to do with your class but with the data you did store in your collection. It seems that in your collection you have stored a ViewObject which internally does contain an EnumerableCollectionView object.
When you serialize data you must be sure what parts of your object graph you do want to serialize. Just putting objects in your collection could cause half of you application data sent over the wire or to disc. There is a good reason why DataContractSerializer was invented.
You should know before the serialize call what data you are going to serialize. Otherwise it could happen that e.g. in a client server application you are trying to deserialize types which are located in assemblies that do exist only on the server.
Related Topics
Getting a System.Type from Type's Partial Name
Right Aligning Text in PDFpcell
Different Like Behaviour Between My Application and the Access Query Wizard
How to Fix Error: "Could Not Find Schema Information for the Attribute/Element" by Creating Schema
Enable Entity Framework 6 for MySQL (C#) in Winforms of Microsoft Visual Studio 2013
Using the C# Dispatcher in Wpf Applications
Unhandled Exceptions in Backgroundworker
How to Perform a Cross Join with Linq to SQL
Running Msbuild Programmatically
Custom Attribute on Property - Getting Type and Value of Attributed Property
Better Way to Trigger Onpropertychanged