Index: /trunk/sources/HeuristicLab.Collections/3.3/HeuristicLab.Collections-3.3.csproj
===================================================================
--- /trunk/sources/HeuristicLab.Collections/3.3/HeuristicLab.Collections-3.3.csproj	(revision 8609)
+++ /trunk/sources/HeuristicLab.Collections/3.3/HeuristicLab.Collections-3.3.csproj	(revision 8610)
@@ -114,4 +114,5 @@
   <ItemGroup>
     <None Include="Plugin.cs.frame" />
+    <Compile Include="ReadOnlyObservableKeyedList.cs" />
     <Compile Include="INotifyObservableListItemsChanged.cs" />
     <Compile Include="INotifyObservableKeyedCollectionItemsChanged.cs" />
@@ -119,4 +120,6 @@
     <Compile Include="INotifyObservableCollectionItemsChanged.cs" />
     <Compile Include="INotifyObservableArrayItemsChanged.cs" />
+    <Compile Include="IObservableKeyedList.cs" />
+    <Compile Include="ObservableKeyedList.cs" />
     <Compile Include="Plugin.cs" />
     <Compile Include="ReadOnlyObservableArray.cs" />
@@ -189,5 +192,5 @@
   -->
   <PropertyGroup>
-   <PreBuildEvent Condition=" '$(OS)' == 'Windows_NT' ">set Path=%25Path%25;$(ProjectDir);$(SolutionDir)
+    <PreBuildEvent Condition=" '$(OS)' == 'Windows_NT' ">set Path=%25Path%25;$(ProjectDir);$(SolutionDir)
 set ProjectDir=$(ProjectDir)
 set SolutionDir=$(SolutionDir)
@@ -196,5 +199,5 @@
 call PreBuildEvent.cmd
 </PreBuildEvent>
-<PreBuildEvent Condition=" '$(OS)' != 'Windows_NT' ">
+    <PreBuildEvent Condition=" '$(OS)' != 'Windows_NT' ">
 export ProjectDir=$(ProjectDir)
 export SolutionDir=$(SolutionDir)
Index: /trunk/sources/HeuristicLab.Collections/3.3/IObservableKeyedList.cs
===================================================================
--- /trunk/sources/HeuristicLab.Collections/3.3/IObservableKeyedList.cs	(revision 8610)
+++ /trunk/sources/HeuristicLab.Collections/3.3/IObservableKeyedList.cs	(revision 8610)
@@ -0,0 +1,27 @@
+﻿#region License Information
+
+/* HeuristicLab
+ * Copyright (C) 2002-2012 Heuristic and Evolutionary Algorithms Laboratory (HEAL)
+ *
+ * This file is part of HeuristicLab.
+ *
+ * HeuristicLab is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * HeuristicLab is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with HeuristicLab. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#endregion
+
+namespace HeuristicLab.Collections {
+  public interface IObservableKeyedList<TKey, TItem> : IObservableKeyedCollection<TKey, TItem>, IObservableList<TItem> {
+  }
+}
Index: /trunk/sources/HeuristicLab.Collections/3.3/ObservableKeyedList.cs
===================================================================
--- /trunk/sources/HeuristicLab.Collections/3.3/ObservableKeyedList.cs	(revision 8610)
+++ /trunk/sources/HeuristicLab.Collections/3.3/ObservableKeyedList.cs	(revision 8610)
@@ -0,0 +1,175 @@
+﻿#region License Information
+
+/* HeuristicLab
+ * Copyright (C) 2002-2012 Heuristic and Evolutionary Algorithms Laboratory (HEAL)
+ *
+ * This file is part of HeuristicLab.
+ *
+ * HeuristicLab is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * HeuristicLab is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with HeuristicLab. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#endregion
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using HeuristicLab.Persistence.Default.CompositeSerializers.Storable;
+
+namespace HeuristicLab.Collections {
+  [Serializable]
+  [StorableClass]
+  public abstract class ObservableKeyedList<TKey, TItem> : ObservableList<TItem>, IObservableKeyedList<TKey, TItem> {
+
+    private Dictionary<TKey, TItem> dict;
+    [Storable]
+    protected Dictionary<TKey, TItem> Dictionary {
+      get { return dict; }
+      private set { dict = value; }
+    }
+    protected IEqualityComparer<TKey> Comparer {
+      get { return dict.Comparer; }
+    }
+
+    public TItem this[TKey key] {
+      get { return dict[key]; }
+    }
+
+    #region constructors
+    protected ObservableKeyedList()
+      : base() {
+      dict = new Dictionary<TKey, TItem>();
+    }
+    protected ObservableKeyedList(int capacity)
+      : base(capacity) {
+      dict = new Dictionary<TKey, TItem>(capacity);
+    }
+    protected ObservableKeyedList(IEqualityComparer<TKey> comparer)
+      : base() {
+      dict = new Dictionary<TKey, TItem>(comparer);
+    }
+    protected ObservableKeyedList(int capacity, IEqualityComparer<TKey> comparer)
+      : base(capacity) {
+      dict = new Dictionary<TKey, TItem>(capacity, comparer);
+    }
+    [StorableConstructor]
+    protected ObservableKeyedList(bool deserializing) : base(deserializing) { }
+    #endregion
+
+    protected abstract TKey GetKeyForItem(TItem item);
+    protected void UpdateItemKey(TItem item) {
+      if (item == null) throw new ArgumentNullException();
+      var dictionaryItem = dict.FirstOrDefault(i => i.Value.Equals(item));
+      if (dictionaryItem.Equals(default(KeyValuePair<TKey, TItem>))) throw new ArgumentException("Item not found");
+
+      dict.Remove(dictionaryItem.Key);
+      dict.Add(GetKeyForItem(item), item);
+      int index = IndexOf(item);
+      OnItemsReplaced(new[] { new IndexedItem<TItem>(index, item) }, new[] { new IndexedItem<TItem>(index, item) });
+      OnItemsReplaced(new[] { item }, new[] { item });
+      OnPropertyChanged("Item[]");
+    }
+
+    #region methods
+    public new ReadOnlyObservableKeyedList<TKey, TItem> AsReadOnly() {
+      return new ReadOnlyObservableKeyedList<TKey, TItem>(this);
+    }
+
+    public bool ContainsKey(TKey key) {
+      return dict.ContainsKey(key);
+    }
+    public new bool Contains(TItem item) {
+      TKey key = GetKeyForItem(item);
+      return ContainsKey(key);
+    }
+
+    public bool TryGetValue(TKey key, out TItem item) {
+      return dict.TryGetValue(key, out item);
+    }
+
+    public bool Remove(TKey key) {
+      TItem item;
+      if (!TryGetValue(key, out item)) return false;
+      return base.Remove(item);
+    }
+    #endregion
+
+
+    #region Events
+    [field: NonSerialized]
+    private event CollectionItemsChangedEventHandler<TItem> itemsReplaced;
+    event CollectionItemsChangedEventHandler<TItem> INotifyObservableKeyedCollectionItemsChanged<TKey, TItem>.ItemsReplaced {
+      add { itemsReplaced += value; }
+      remove { itemsReplaced -= value; }
+    }
+    private void OnItemsReplaced(IEnumerable<TItem> items, IEnumerable<TItem> oldItems) {
+      var handler = itemsReplaced;
+      if (handler != null) handler(this, new CollectionItemsChangedEventArgs<TItem>(items, oldItems));
+    }
+
+    protected override void OnItemsAdded(IEnumerable<IndexedItem<TItem>> items) {
+      UpdateDictionary(items, Enumerable.Empty<IndexedItem<TItem>>());
+      base.OnItemsAdded(items);
+    }
+
+    protected override void OnItemsRemoved(IEnumerable<IndexedItem<TItem>> items) {
+      UpdateDictionary(Enumerable.Empty<IndexedItem<TItem>>(), items);
+      base.OnItemsRemoved(items);
+    }
+
+    protected override void OnItemsReplaced(IEnumerable<IndexedItem<TItem>> items, IEnumerable<IndexedItem<TItem>> oldItems) {
+      UpdateDictionary(items, oldItems);
+      base.OnItemsReplaced(items, oldItems);
+    }
+
+    protected override void OnCollectionReset(IEnumerable<IndexedItem<TItem>> items, IEnumerable<IndexedItem<TItem>> oldItems) {
+      UpdateDictionary(items, oldItems);
+      base.OnCollectionReset(items, oldItems);
+    }
+    #endregion
+
+    #region helper methods
+    private void UpdateDictionary(IEnumerable<IndexedItem<TItem>> items, IEnumerable<IndexedItem<TItem>> oldItems) {
+      foreach (var item in oldItems)
+        dict.Remove(GetKeyForItem(item.Value));
+
+      bool duplicateKeyFound = false;
+      foreach (var item in items) {
+        TKey key = GetKeyForItem(item.Value);
+        if (dict.ContainsKey(key)) {
+          duplicateKeyFound = true;
+          break;
+        }
+        dict.Add(key, item.Value);
+      }
+
+      //restore old state of list and dictionary
+      if (duplicateKeyFound) {
+        foreach (var item in items.OrderByDescending(i => i.Index)) {
+          list.RemoveAt(item.Index);
+        }
+        foreach (var item in items.Select(i => i.Value).Except(list)) {
+          TKey key = GetKeyForItem(item);
+          dict.Remove(key);
+        }
+        foreach (var item in oldItems.OrderByDescending(i => i.Index)) {
+          list.Insert(item.Index, item.Value);
+          TKey key = GetKeyForItem(item.Value);
+          dict.Add(key, item.Value);
+        }
+        throw new ArgumentException("An element with the same key already exists.");
+      }
+    }
+    #endregion
+  }
+}
Index: /trunk/sources/HeuristicLab.Collections/3.3/ObservableList.cs
===================================================================
--- /trunk/sources/HeuristicLab.Collections/3.3/ObservableList.cs	(revision 8609)
+++ /trunk/sources/HeuristicLab.Collections/3.3/ObservableList.cs	(revision 8610)
@@ -158,10 +158,10 @@
       int capacity = list.Capacity;
       list.Add(item);
+      OnItemsAdded(new IndexedItem<T>[] { new IndexedItem<T>(list.Count - 1, item) });
+      OnItemsAdded(new T[] { item });
       if (list.Capacity != capacity)
         OnPropertyChanged("Capacity");
       OnPropertyChanged("Item[]");
       OnPropertyChanged("Count");
-      OnItemsAdded(new IndexedItem<T>[] { new IndexedItem<T>(list.Count - 1, item) });
-      OnItemsAdded(new T[] { item });
     }
     public void AddRange(IEnumerable<T> collection) {
@@ -175,10 +175,10 @@
       }
       if (items.Count > 0) {
+        OnItemsAdded(items);
+        OnItemsAdded(collection);
         if (list.Capacity != capacity)
           OnPropertyChanged("Capacity");
         OnPropertyChanged("Item[]");
         OnPropertyChanged("Count");
-        OnItemsAdded(items);
-        OnItemsAdded(collection);
       }
     }
@@ -187,10 +187,10 @@
       int capacity = list.Capacity;
       list.Insert(index, item);
+      OnItemsAdded(new IndexedItem<T>[] { new IndexedItem<T>(index, item) });
+      OnItemsAdded(new T[] { item });
       if (list.Capacity != capacity)
         OnPropertyChanged("Capacity");
       OnPropertyChanged("Item[]");
       OnPropertyChanged("Count");
-      OnItemsAdded(new IndexedItem<T>[] { new IndexedItem<T>(index, item) });
-      OnItemsAdded(new T[] { item });
     }
     public void InsertRange(int index, IEnumerable<T> collection) {
@@ -203,10 +203,10 @@
       }
       if (items.Count > 0) {
+        OnItemsAdded(items);
+        OnItemsAdded(collection);
         if (list.Capacity != capacity)
           OnPropertyChanged("Capacity");
         OnPropertyChanged("Item[]");
         OnPropertyChanged("Count");
-        OnItemsAdded(items);
-        OnItemsAdded(collection);
       }
     }
@@ -229,8 +229,8 @@
       else items = new List<IndexedItem<T>>();
 
+      OnItemsReplaced(items, oldItems);
       if (oldCapacity != list.Capacity) OnPropertyChanged("Capacity");
       OnPropertyChanged("Item[]");
       if (oldItems.Count != items.Count) OnPropertyChanged("Count");
-      OnItemsReplaced(items, oldItems);
     }
 
@@ -239,8 +239,8 @@
       if (index != -1) {
         list.RemoveAt(index);
-        OnPropertyChanged("Item[]");
-        OnPropertyChanged("Count");
         OnItemsRemoved(new IndexedItem<T>[] { new IndexedItem<T>(index, item) });
         OnItemsRemoved(new T[] { item });
+        OnPropertyChanged("Item[]");
+        OnPropertyChanged("Count");
         return true;
       }
@@ -260,8 +260,8 @@
       if (indexedItems.Count > 0) {
         result = list.RemoveAll(match);
-        OnPropertyChanged("Item[]");
-        OnPropertyChanged("Count");
         OnItemsRemoved(indexedItems);
         OnItemsRemoved(items);
+        OnPropertyChanged("Item[]");
+        OnPropertyChanged("Count");
       }
       return result;
@@ -270,8 +270,8 @@
       T item = list[index];
       list.RemoveAt(index);
+      OnItemsRemoved(new IndexedItem<T>[] { new IndexedItem<T>(index, item) });
+      OnItemsRemoved(new T[] { item });
       OnPropertyChanged("Item[]");
       OnPropertyChanged("Count");
-      OnItemsRemoved(new IndexedItem<T>[] { new IndexedItem<T>(index, item) });
-      OnItemsRemoved(new T[] { item });
     }
     public void RemoveRange(int index, int count) {
@@ -281,8 +281,8 @@
         list.CopyTo(index, items, 0, count);
         list.RemoveRange(index, count);
-        OnPropertyChanged("Item[]");
-        OnPropertyChanged("Count");
         OnItemsRemoved(indexedItems);
         OnItemsRemoved(items);
+        OnPropertyChanged("Item[]");
+        OnPropertyChanged("Count");
       }
     }
@@ -293,8 +293,8 @@
         T[] items = list.ToArray();
         list.Clear();
-        OnPropertyChanged("Item[]");
-        OnPropertyChanged("Count");
         OnCollectionReset(new IndexedItem<T>[0], indexedItems);
         OnCollectionReset(new T[0], items);
+        OnPropertyChanged("Item[]");
+        OnPropertyChanged("Count");
       }
     }
@@ -304,6 +304,6 @@
         IndexedItem<T>[] oldItems = GetIndexedItems();
         list.Reverse();
-        OnPropertyChanged("Item[]");
         OnItemsMoved(GetIndexedItems(), oldItems);
+        OnPropertyChanged("Item[]");
       }
     }
@@ -312,6 +312,6 @@
         IndexedItem<T>[] oldItems = GetIndexedItems(index, count);
         list.Reverse(index, count);
-        OnPropertyChanged("Item[]");
         OnItemsMoved(GetIndexedItems(index, count), oldItems);
+        OnPropertyChanged("Item[]");
       }
     }
@@ -321,6 +321,6 @@
         IndexedItem<T>[] oldItems = GetIndexedItems();
         list.Sort();
-        OnPropertyChanged("Item[]");
         OnItemsMoved(GetIndexedItems(), oldItems);
+        OnPropertyChanged("Item[]");
       }
     }
@@ -329,6 +329,6 @@
         IndexedItem<T>[] oldItems = GetIndexedItems();
         list.Sort(comparison);
-        OnPropertyChanged("Item[]");
         OnItemsMoved(GetIndexedItems(), oldItems);
+        OnPropertyChanged("Item[]");
       }
     }
@@ -337,6 +337,6 @@
         IndexedItem<T>[] oldItems = GetIndexedItems();
         list.Sort(comparer);
-        OnPropertyChanged("Item[]");
         OnItemsMoved(GetIndexedItems(), oldItems);
+        OnPropertyChanged("Item[]");
       }
     }
@@ -345,6 +345,6 @@
         IndexedItem<T>[] oldItems = GetIndexedItems(index, count);
         list.Sort(index, count, comparer);
-        OnPropertyChanged("Item[]");
         OnItemsMoved(GetIndexedItems(index, count), oldItems);
+        OnPropertyChanged("Item[]");
       }
     }
@@ -355,4 +355,5 @@
       return new ReadOnlyObservableList<T>(this);
     }
+
     public T[] ToArray() {
       return list.ToArray();
Index: /trunk/sources/HeuristicLab.Collections/3.3/ReadOnlyObservableKeyedList.cs
===================================================================
--- /trunk/sources/HeuristicLab.Collections/3.3/ReadOnlyObservableKeyedList.cs	(revision 8610)
+++ /trunk/sources/HeuristicLab.Collections/3.3/ReadOnlyObservableKeyedList.cs	(revision 8610)
@@ -0,0 +1,89 @@
+﻿#region License Information
+/* HeuristicLab
+ * Copyright (C) 2002-2012 Heuristic and Evolutionary Algorithms Laboratory (HEAL)
+ *
+ * This file is part of HeuristicLab.
+ *
+ * HeuristicLab is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * HeuristicLab is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with HeuristicLab. If not, see <http://www.gnu.org/licenses/>.
+ */
+#endregion
+
+using System;
+using System.Collections.Generic;
+using HeuristicLab.Persistence.Default.CompositeSerializers.Storable;
+
+namespace HeuristicLab.Collections {
+  [StorableClass]
+  [Serializable]
+  public class ReadOnlyObservableKeyedList<TKey, TItem> : ReadOnlyObservableList<TItem>, IObservableKeyedList<TKey, TItem> {
+
+    protected IObservableKeyedList<TKey, TItem> KeyedList {
+      get { return (IObservableKeyedList<TKey, TItem>)base.list; }
+    }
+
+    bool ICollection<TItem>.IsReadOnly {
+      get { return true; }
+    }
+    public TItem this[TKey key] {
+      get { return KeyedList[key]; }
+    }
+
+    #region Constructors
+    protected ReadOnlyObservableKeyedList() { }
+    public ReadOnlyObservableKeyedList(IObservableKeyedList<TKey, TItem> keyedList)
+      : base(keyedList) {
+      RegisterEvents();
+    }
+    [StorableConstructor]
+    protected ReadOnlyObservableKeyedList(bool deserializing) { }
+    [StorableHook(HookType.AfterDeserialization)]
+    private void AfterDeserialization() {
+      RegisterEvents();
+    }
+    #endregion
+
+    public bool ContainsKey(TKey key) {
+      return KeyedList.ContainsKey(key);
+    }
+    public bool TryGetValue(TKey key, out TItem item) {
+      return KeyedList.TryGetValue(key, out item);
+    }
+
+    bool IObservableKeyedCollection<TKey, TItem>.Remove(TKey key) {
+      throw new NotSupportedException();
+    }
+
+    #region events
+    private void RegisterEvents() {
+      ((INotifyObservableKeyedCollectionItemsChanged<TKey, TItem>)KeyedList).ItemsReplaced += keyedList_ItemsReplaced;
+    }
+
+    [field: NonSerialized]
+    private event CollectionItemsChangedEventHandler<TItem> itemsReplaced;
+    event CollectionItemsChangedEventHandler<TItem> INotifyObservableKeyedCollectionItemsChanged<TKey, TItem>.ItemsReplaced {
+      add { itemsReplaced += value; }
+      remove { itemsReplaced -= value; }
+    }
+    private void OnItemsReplaced(IEnumerable<TItem> items, IEnumerable<TItem> oldItems) {
+      var handler = itemsReplaced;
+      if (handler != null) handler(this, new CollectionItemsChangedEventArgs<TItem>(items, oldItems));
+    }
+
+    private void keyedList_ItemsReplaced(object sender, CollectionItemsChangedEventArgs<TItem> e) {
+      OnItemsReplaced(e.Items, e.OldItems);
+    }
+
+    #endregion
+  }
+}
Index: /trunk/sources/HeuristicLab.Collections/3.3/ReadOnlyObservableList.cs
===================================================================
--- /trunk/sources/HeuristicLab.Collections/3.3/ReadOnlyObservableList.cs	(revision 8609)
+++ /trunk/sources/HeuristicLab.Collections/3.3/ReadOnlyObservableList.cs	(revision 8610)
@@ -120,5 +120,5 @@
 
     #region Events
-    protected void RegisterEvents() {
+    private void RegisterEvents() {
       list.ItemsAdded += new CollectionItemsChangedEventHandler<IndexedItem<T>>(list_ItemsAdded);
       ((IObservableCollection<T>)list).ItemsAdded += new CollectionItemsChangedEventHandler<T>(list_ItemsAdded);
Index: /trunk/sources/HeuristicLab.Core/3.3/Collections/ReadOnlyItemList.cs
===================================================================
--- /trunk/sources/HeuristicLab.Core/3.3/Collections/ReadOnlyItemList.cs	(revision 8609)
+++ /trunk/sources/HeuristicLab.Core/3.3/Collections/ReadOnlyItemList.cs	(revision 8610)
@@ -48,8 +48,7 @@
     [StorableConstructor]
     protected ReadOnlyItemList(bool deserializing) : base(deserializing) { }
-    protected ReadOnlyItemList(ReadOnlyItemList<T> original, Cloner cloner) {
+    protected ReadOnlyItemList(ReadOnlyItemList<T> original, Cloner cloner)
+      : base(cloner.Clone((IItemList<T>)original.list)) {
       cloner.RegisterClonedObject(original, this);
-      list = cloner.Clone((IItemList<T>)original.list);
-      RegisterEvents();
     }
     public ReadOnlyItemList() : base(new ItemList<T>()) { }
