none
Updating a ListCollectionView object from a KeyedCollection and vice versa<> object RRS feed

  • Question

  • Hi all,

    I have a data model class Scenario describing a bunch of data fields. This is wrapped in a class Container which contains information where an how to store a Scenario object. In another class ContainerServices a KeyedCollection<string, Container> is used to maintain a collection of Container objects selectable by one of its fields. This works fine with all transactions against the KeyedCollection objects. Only the creation of the Dictionary object of KeyedCollection must be enforced by an Add/Remove sequence. But this is not part of my problem. The collection in the Items property of KeyedCollection is casted as a List<Container> which properly show the list of my Container objects.

    Here are code snippets how I subclassed KeyedCollection:

    public class ContainerDictionary : KeyedCollection<string, Container> {
    	public ContainerDictionary() : base(new NamesEqual(), 0) {}
    	protected override string GetKeyForItem(Container container) {return container.Name.ToLowerInvariant(); }
    	public Dictionary<string, Container> ScenarioNames { get { return base.Dictionary as Dictionary<string, Container>; } }
    	public List<Container> ContainerObjects { get { return base.Items as List<Container>; } }
    	}

    ... and how I used this derived class:

    public ContainerDictionary ContainerDict { get { return containerDict; } private set { containerDict = value; OnChanged("ContainerDict"); } }
    public Dictionary<string, Container>.KeyCollection ContainerNames { get { return containerNames; } private set { containerNames = value; OnChanged("ContainerNames"); } }
    public List<Container> ContainerObjects { get { return containerObjects; } private set { containerObjects = value; OnChanged("ContainerObjects"); } }
    
    private ContainerDictionary containerDict;
    private List<Container> containerObjects;
    private Dictionary<string, Container>.KeyCollection containerNames;
     <snip>
    containerDict = new ContainerDictionary();
    dictAdd(new Container("Szenario (C)", ContainerParent)); //... to force creation ...
    dictRemove("Szenario (C)"); //... of the Dictionary object because ...
    containerNames = containerDict.ScenarioNames.Keys; //... it works as bad as designed
    ContainerObjects = containerDict.ContainerObjects;

    This works well and all transaction against object containerDict are nicely reflected in ContainerObjects and ContainerNames properties.

    My problem is, that all my trials to ship the ContainerObjects to a ListCollectionView are only reflected once after creation of the ListCollectionView<Container> object and all transactions against the underlying List<Container> are ignored.

    This is my best guess how it may be coded, which is in in vain, I'm afraid.

    public class ContainerCollection : ObservableCollection<Container> {
    	public ContainerCollection() : base() {}
    	public ContainerCollection(List<Container> list) : base(list) {}
    	}
    
    public ListCollectionView ContainerViewData { get; private set; }
    <snip>
    ContainerCollection conTest = new ContainerCollection(ContainerObjects);
    ContainerViewData = new ListCollectionView(conTest);

    So my problem is: How can I ship transactions against the KeyedCollection<string Container> to the ListCollectionView?


    Regards Uwe



    • Edited by Jörg Debus Friday, March 1, 2013 10:19 AM typos
    Monday, February 25, 2013 12:25 PM

Answers

  • Hi,

    Note that KeyedCollection<TKey, TItem> is inherited from Collection<TItem>, which implements IList<TItem> interface.

    It is only necessary for your ContainerObjects property to change the return type and return ContainerDictionary object itself.

    Alternatively, you can initialize ListCollectionView with ContainerDictionary object. The ListCollectionView propagates the changes raised by the CollectionChanged event iff the source collection implements the INotifyCollectionChanged interface.

    //ContainerViewData = new ListCollectionView(containerDict.ContainerObjects);
    ContainerViewData = new ListCollectionView(containerDict);
    
    Debug.Assert(containerDict.CollectionChanged != null);

    Best regards

    Friday, March 1, 2013 1:51 AM
  • Hi all,

    this is the class KeyedObservableCollection and an associated class KeysEqual I promised to publish. Have fun!

    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.Collections.Specialized;
    using System.ComponentModel;
    using System.Text;
    
    namespace PamServices.Classes {
    
    	//*---* *------------------------------------------------------*
    	///<summary>
    	///		Class KeyedObservableCollection describes a collection 
    	///		that holds and maintains keyes taken from the collection
    	///		objects.
    	///</summary>
    	///<remarks>
    	///		This class together an EqualityComparer class KeysEqual
    	///		assist you to normalize keys of class string for comparison. 
    	///</remarks>
    	///
    	public class KeyedObservableCollection<TKey, TItem> : KeyedCollection<TKey, TItem>, INotifyCollectionChanged {
    		private Func<TItem, TKey> _getKeyFromItem;					//*---> e.g. (Func<MyData, string>)((d) => d.MyKey);
    		private Func<TKey, TKey> _normalizeKey;						//*---> e.g. (Func<string, string>)((s) => s.ToLowerInvariant());
    		private KeysEqual<TKey> comparer;
    
    		//*--->	Properties and Indexer
    
    		public Dictionary<TKey, TItem> ScenarioNames { get { return base.Dictionary as Dictionary<TKey, TItem>; } }
    		public new TItem this[TKey key] { get { return base[_normalizeKey(key)]; } }
    
    		//*--->	Constructors
    
    		/// <summary>Creates an object using unnormalized strings as keys.</summary>
    		/// <param name="getKeyFromItem">specifies a delegate to get the key from item.</param>
    		public KeyedObservableCollection(Func<TItem, TKey> getKeyFromItem) : base() {
    			if (getKeyFromItem == null)
    				throw new ArgumentNullException(string.Format("\"getKeyFromItem\" - Delegate to get the key as a {0} from {1} must be specified.", typeof(TKey).Name, typeof(TItem).Name));
    			this.comparer = new KeysEqual<TKey>();
    			this._normalizeKey = this.comparer.NormalizeKey;
    			this._getKeyFromItem = getKeyFromItem;
    			}
    
    		/// <summary>Creates an object using normalized keys.</summary>
    		/// <param name="getKeyFromItem">specifies a delegate to get the key from item.</param>
    		/// <param name="comparer">specifies a comparer of class KeysEqual initialized with a delegate to normalize keys.</param>
    		public KeyedObservableCollection(Func<TItem, TKey> getKeyFromItem, KeysEqual<TKey> comparer)
    			: base(comparer) {
    			if (getKeyFromItem == null)
    				throw new ArgumentNullException(string.Format("\"getKeyFromItem\" - Delegate to get the key as a {0} from {1} must be specified.", typeof(TKey).Name, typeof(TItem).Name));
    			this.comparer = base.Comparer as KeysEqual<TKey>;
    			this._normalizeKey = this.comparer.NormalizeKey;
    			this._getKeyFromItem = getKeyFromItem;
    			}
    
    		//*---> New methods	or wrappers	for normalizing the keys
    
    		public void Replace(TItem item, TItem newItem) {
    			this.SetItem(base.IndexOf(item), newItem);
    			}
    
    		public new bool Remove(TKey key) {
    			return base.Remove(_normalizeKey(key));
    			}
    
    		public new bool Remove(TItem item) {
    			return base.Remove(item);
    			}
    
    		public new bool Contains(TKey key) {
    			return base.Contains(_normalizeKey(key));
    			}
    
    		public new bool Contains(TItem item) {
    			return base.Contains(item);
    			}
    
    		//*--->	Overrides
    
    		protected override TKey GetKeyForItem(TItem item) { return _normalizeKey(_getKeyFromItem(item)); }
    
    		protected override void InsertItem(int index, TItem item) {
    			base.InsertItem(index, item);
    			OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, index));
    			}
    
    		protected override void RemoveItem(int index) {
    			TItem item = this[index];
    			base.RemoveItem(index);
    			OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item));
    			}
    
    		protected override void SetItem(int index, TItem newItem) {
    			TItem item = this[index];
    			base.SetItem(index, newItem);
    			OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item));
    			OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, newItem, index));
    			}
    
    		protected override void ClearItems() {
    			base.ClearItems();
    			OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
    			}
    
    		//*---> Raise CollectionChanged
    
    		public event NotifyCollectionChangedEventHandler CollectionChanged;
    		protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs e) {
    			var handler = CollectionChanged; if (handler == null) return;
    			handler(this, e);
    			}
    		}
    
    	//*---* *------------------------------------------------------*
    	///<summary>
    	///		Class KeysEqual is a generic equality comparer that is 
    	///		used to determine equality of simple keys in the collection.
    	///</summary>
    	///<remarks>
    	///		Keys can be normalized here to represent equality from 
    	///		an application point of view (e.g. strings	are normalized
    	///		to InvariantCultureIgnoreCase if filenames are the key, etc.)
    	///		A Delegate can be specified to perform this task.
    	///		Remark
    	///		------
    	///		This Delegate is also used to normalize TKeys in the
    	///		collection overrides.
    	///</remarks>
    	///
    	public class KeysEqual<TKey> : EqualityComparer<TKey> {
    		private Func<TKey, TKey> _normalizeKey;
    		internal Func<TKey, TKey> NormalizeKey { get { return _normalizeKey; } }
    
    		public KeysEqual() {_normalizeKey = (Func<TKey, TKey>)((s) => s);}						//*---> (Func<string, string>)((s) => s.ToLowerInvariant()); 
    		public KeysEqual(Func<TKey, TKey> normalizeKey) { this._normalizeKey = normalizeKey; }	//		could be used for for simple strings to ignore caps
    
    		public override bool Equals(TKey x, TKey y) {
    			return _normalizeKey(x).Equals(_normalizeKey(y));
    			}
    		public override int GetHashCode(TKey key) {
    			return _normalizeKey(key).GetHashCode();
    			}
    		}
    
    	}
    Comments and suggestions are welcome.


    Regards Uwe


    • Edited by Jörg Debus Monday, March 4, 2013 9:05 AM Typos
    • Marked as answer by Jörg Debus Monday, March 4, 2013 9:06 AM
    Monday, March 4, 2013 9:04 AM
  • Hi,

    ObservableCollection<T>(List<T>) constructor copies the elements from the specified List<T>. So all transactions against the underlying List<Container> are ignored.

    One of the solutions is the implementation of observable KeyedCollection. For example,

    public class ContainerDictionary : KeyedCollection<string, Container>, INotifyCollectionChanged { public event NotifyCollectionChangedEventHandler CollectionChanged; private void
    OnCollectionChanged(NotifyCollectionChangedEventArgs e) { if (CollectionChanged != null) CollectionChanged(this, e); } protected override void InsertItem(int index, Container item) { base.InsertItem(index, item); OnCollectionChanged(new NotifyCollectionChangedEventArgs( NotifyCollectionChangedAction.Add, item, index)); } // ... }

    <snip>
    ContainerViewData = new ListCollectionView(containerDict);

    cf.

    Make KeyedCollection<TKey, TItem> to work properly with WPF data binding

    http://geekswithblogs.net/NewThingsILearned/archive/2010/01/12/make-keyedcollectionlttkey-titemgt-to-work-properly-with-wpf-data-binding.aspx

    Best regards

    • Edited by octopus-jelly Monday, February 25, 2013 3:41 PM
    • Marked as answer by Jörg Debus Monday, February 25, 2013 7:16 PM
    Monday, February 25, 2013 2:35 PM

All replies

  • Hi,

    ObservableCollection<T>(List<T>) constructor copies the elements from the specified List<T>. So all transactions against the underlying List<Container> are ignored.

    One of the solutions is the implementation of observable KeyedCollection. For example,

    public class ContainerDictionary : KeyedCollection<string, Container>, INotifyCollectionChanged { public event NotifyCollectionChangedEventHandler CollectionChanged; private void
    OnCollectionChanged(NotifyCollectionChangedEventArgs e) { if (CollectionChanged != null) CollectionChanged(this, e); } protected override void InsertItem(int index, Container item) { base.InsertItem(index, item); OnCollectionChanged(new NotifyCollectionChangedEventArgs( NotifyCollectionChangedAction.Add, item, index)); } // ... }

    <snip>
    ContainerViewData = new ListCollectionView(containerDict);

    cf.

    Make KeyedCollection<TKey, TItem> to work properly with WPF data binding

    http://geekswithblogs.net/NewThingsILearned/archive/2010/01/12/make-keyedcollectionlttkey-titemgt-to-work-properly-with-wpf-data-binding.aspx

    Best regards

    • Edited by octopus-jelly Monday, February 25, 2013 3:41 PM
    • Marked as answer by Jörg Debus Monday, February 25, 2013 7:16 PM
    Monday, February 25, 2013 2:35 PM
  • Hi octopus-jelly,

    I carefully implemented your coding from the given link. It compiles fine and the derived class runs all transactions against KeyedCollection objects properly. Unfortunately my problem persists: the ListCollectionView objects that should manage the KeyedCollection shows insertions and removes only after calling its Refresh-method. Tracing thru the ObservableKeyedCollection calls shows, that CollectionChanged Event is always null and therefore no event is fired.

    Here is the object creation sequence (Class ContainerDictionary is the renamed ObservableKeyedCollection):

    containerDict = new ContainerDictionary<string, Container>(new NamesEqual<string>(), (Func<Container, string>)((s) => s.Name.ToLowerInvariant()));
    dictStart();
    ContainerNames = containerDict.ScenarioNames.Keys;
    ContainerViewData = new ListCollectionView(containerDict.ContainerObjects);

    This is how I add new items:

    private void dictAdd(Container container) {
    	ContainerDict.Add(container);
    	}

    And this is the Event raising sequence in ObservableKeyed Collection:

    public event NotifyCollectionChangedEventHandler CollectionChanged;
    protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs e) {
    	if (CollectionChanged != null) CollectionChanged(this, e);
    	}

    So my best guess is that ListCollectionView does not subscribe to the CollectionChanged event and because of this the inserts are not recognized.

    My experience with WPF collection is rather limited I'm afraid. So I fully rely on yours!

    Thanx in advance.


    Regards Uwe



    • Edited by Jörg Debus Thursday, February 28, 2013 1:26 PM Typos
    Thursday, February 28, 2013 1:23 PM
  • Hi,

    Note that KeyedCollection<TKey, TItem> is inherited from Collection<TItem>, which implements IList<TItem> interface.

    It is only necessary for your ContainerObjects property to change the return type and return ContainerDictionary object itself.

    Alternatively, you can initialize ListCollectionView with ContainerDictionary object. The ListCollectionView propagates the changes raised by the CollectionChanged event iff the source collection implements the INotifyCollectionChanged interface.

    //ContainerViewData = new ListCollectionView(containerDict.ContainerObjects);
    ContainerViewData = new ListCollectionView(containerDict);
    
    Debug.Assert(containerDict.CollectionChanged != null);

    Best regards

    Friday, March 1, 2013 1:51 AM
  • Hi octopus-jelly,

    that did the trick! Surprisingly enough, the keys are also updated when I insert/remove thru the View using ContainerViewData.InsertNewItem(...) to add/remove items. So data integrity is also maintained when updates come from the View via Binding. It's a great class now.

    I will investigate a little deeper to learn like "what happens when the key-property in an collection item are changed" and so on.

    Thanks a lot for your fantastic and prompt assistance.


    Regards Uwe

    Friday, March 1, 2013 10:18 AM
  • "what happens when the key-property in an collection item are changed"

    cf. For your information, see the examples of 'KeyedCollection<TKey, TItem>.ChangeItemKey Method':

    http://msdn.microsoft.com/en-us/library/ms132449.aspx

    Friday, March 1, 2013 11:38 AM
  • Hi all,

    with the great help from octopus-jelly I have coded based on his ObservableKeyedCollection a class KeyedObservableCollection. This class has been tested with class  ListCollectionView and works well synchronized from in both directions. I also added a Replace(item, newItem) method which is not publicly available in the original KeyedCollection. Furthermore I added a new delegate to support normalization of keys.

    I will soon publish the source here.


    Regards Uwe

    Saturday, March 2, 2013 5:32 PM
  • Hi all,

    this is the class KeyedObservableCollection and an associated class KeysEqual I promised to publish. Have fun!

    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.Collections.Specialized;
    using System.ComponentModel;
    using System.Text;
    
    namespace PamServices.Classes {
    
    	//*---* *------------------------------------------------------*
    	///<summary>
    	///		Class KeyedObservableCollection describes a collection 
    	///		that holds and maintains keyes taken from the collection
    	///		objects.
    	///</summary>
    	///<remarks>
    	///		This class together an EqualityComparer class KeysEqual
    	///		assist you to normalize keys of class string for comparison. 
    	///</remarks>
    	///
    	public class KeyedObservableCollection<TKey, TItem> : KeyedCollection<TKey, TItem>, INotifyCollectionChanged {
    		private Func<TItem, TKey> _getKeyFromItem;					//*---> e.g. (Func<MyData, string>)((d) => d.MyKey);
    		private Func<TKey, TKey> _normalizeKey;						//*---> e.g. (Func<string, string>)((s) => s.ToLowerInvariant());
    		private KeysEqual<TKey> comparer;
    
    		//*--->	Properties and Indexer
    
    		public Dictionary<TKey, TItem> ScenarioNames { get { return base.Dictionary as Dictionary<TKey, TItem>; } }
    		public new TItem this[TKey key] { get { return base[_normalizeKey(key)]; } }
    
    		//*--->	Constructors
    
    		/// <summary>Creates an object using unnormalized strings as keys.</summary>
    		/// <param name="getKeyFromItem">specifies a delegate to get the key from item.</param>
    		public KeyedObservableCollection(Func<TItem, TKey> getKeyFromItem) : base() {
    			if (getKeyFromItem == null)
    				throw new ArgumentNullException(string.Format("\"getKeyFromItem\" - Delegate to get the key as a {0} from {1} must be specified.", typeof(TKey).Name, typeof(TItem).Name));
    			this.comparer = new KeysEqual<TKey>();
    			this._normalizeKey = this.comparer.NormalizeKey;
    			this._getKeyFromItem = getKeyFromItem;
    			}
    
    		/// <summary>Creates an object using normalized keys.</summary>
    		/// <param name="getKeyFromItem">specifies a delegate to get the key from item.</param>
    		/// <param name="comparer">specifies a comparer of class KeysEqual initialized with a delegate to normalize keys.</param>
    		public KeyedObservableCollection(Func<TItem, TKey> getKeyFromItem, KeysEqual<TKey> comparer)
    			: base(comparer) {
    			if (getKeyFromItem == null)
    				throw new ArgumentNullException(string.Format("\"getKeyFromItem\" - Delegate to get the key as a {0} from {1} must be specified.", typeof(TKey).Name, typeof(TItem).Name));
    			this.comparer = base.Comparer as KeysEqual<TKey>;
    			this._normalizeKey = this.comparer.NormalizeKey;
    			this._getKeyFromItem = getKeyFromItem;
    			}
    
    		//*---> New methods	or wrappers	for normalizing the keys
    
    		public void Replace(TItem item, TItem newItem) {
    			this.SetItem(base.IndexOf(item), newItem);
    			}
    
    		public new bool Remove(TKey key) {
    			return base.Remove(_normalizeKey(key));
    			}
    
    		public new bool Remove(TItem item) {
    			return base.Remove(item);
    			}
    
    		public new bool Contains(TKey key) {
    			return base.Contains(_normalizeKey(key));
    			}
    
    		public new bool Contains(TItem item) {
    			return base.Contains(item);
    			}
    
    		//*--->	Overrides
    
    		protected override TKey GetKeyForItem(TItem item) { return _normalizeKey(_getKeyFromItem(item)); }
    
    		protected override void InsertItem(int index, TItem item) {
    			base.InsertItem(index, item);
    			OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, index));
    			}
    
    		protected override void RemoveItem(int index) {
    			TItem item = this[index];
    			base.RemoveItem(index);
    			OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item));
    			}
    
    		protected override void SetItem(int index, TItem newItem) {
    			TItem item = this[index];
    			base.SetItem(index, newItem);
    			OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item));
    			OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, newItem, index));
    			}
    
    		protected override void ClearItems() {
    			base.ClearItems();
    			OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
    			}
    
    		//*---> Raise CollectionChanged
    
    		public event NotifyCollectionChangedEventHandler CollectionChanged;
    		protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs e) {
    			var handler = CollectionChanged; if (handler == null) return;
    			handler(this, e);
    			}
    		}
    
    	//*---* *------------------------------------------------------*
    	///<summary>
    	///		Class KeysEqual is a generic equality comparer that is 
    	///		used to determine equality of simple keys in the collection.
    	///</summary>
    	///<remarks>
    	///		Keys can be normalized here to represent equality from 
    	///		an application point of view (e.g. strings	are normalized
    	///		to InvariantCultureIgnoreCase if filenames are the key, etc.)
    	///		A Delegate can be specified to perform this task.
    	///		Remark
    	///		------
    	///		This Delegate is also used to normalize TKeys in the
    	///		collection overrides.
    	///</remarks>
    	///
    	public class KeysEqual<TKey> : EqualityComparer<TKey> {
    		private Func<TKey, TKey> _normalizeKey;
    		internal Func<TKey, TKey> NormalizeKey { get { return _normalizeKey; } }
    
    		public KeysEqual() {_normalizeKey = (Func<TKey, TKey>)((s) => s);}						//*---> (Func<string, string>)((s) => s.ToLowerInvariant()); 
    		public KeysEqual(Func<TKey, TKey> normalizeKey) { this._normalizeKey = normalizeKey; }	//		could be used for for simple strings to ignore caps
    
    		public override bool Equals(TKey x, TKey y) {
    			return _normalizeKey(x).Equals(_normalizeKey(y));
    			}
    		public override int GetHashCode(TKey key) {
    			return _normalizeKey(key).GetHashCode();
    			}
    		}
    
    	}
    Comments and suggestions are welcome.


    Regards Uwe


    • Edited by Jörg Debus Monday, March 4, 2013 9:05 AM Typos
    • Marked as answer by Jörg Debus Monday, March 4, 2013 9:06 AM
    Monday, March 4, 2013 9:04 AM