ObservableCollection元素转换/投影包装器

在WPF中创建ViewModel时,有时需要将ObservableCollection (源集合)中可用的数据转换为扩展/限制/投影原始元素(目标集合)的包装元素集合,同时将数据和顺序转换为元素始终镜像原始集合。

就像Select扩展方法一样,除了它不断更新,因此可以用于WPF绑定。

如果在索引x处将元素添加到源,则会在目标集合中的相同索引x处添加相同元素的Wrapper。 如果在源集合中删除了索引y处的元素,则会在目标集合中删除索引y处的元素。

假设有一个ObservableCollection ,但我需要绑定的是ReadOnlyObservableCollection (或等效的),其中ClassB – > ClassA如下:

 class ClassB : INotifyPropertyChanged, IDisposable { public ClassB(ClassA a) { Wrapped = a; (Wrapped as INotifyPropertyChanged).PropertyChanged+=WrappedChanged; } public ClassA Wrapped { get; private set; } public int SomeOtherProperty { get { return SomeFunction(Wrapped); } WrappedChanged(object s, NotifyPropertyChangedArgs a) { ... } ... } 

我可以编写自己的TemplatedTransformCollectionWrapper ,我可以写这个:

 ObservableCollection source; TemplatedTransformCollectionWrapper theCollectionThatWillBeUsedInABinding = TemplatedTransformCollectionWrapper(source, classA => new ClassB(classA)); 

TemplatedTransformCollectionWrapper理想地包装实现INotifyCollectionChanged所有集合,并正确处理原始包装集合的所有可能的添加,删除,替换操作。

正确编写TemplatedTransformCollectionWrapper并不是一件容易的事情,它似乎是其他人已经完成的事情,也许它甚至是核心框架的一部分。 但我找不到它。

我在这里发布我的解决方法 – 这是一个自定义类。 仍希望得到更好的答案。

 using System; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Collections.Specialized; using System.Linq; namespace ViewLayer { public class TransformObservableCollection : INotifyCollectionChanged, IList, IReadOnlyList, IDisposable { public TransformObservableCollection(ObservableCollection wrappedCollection, Func transform) { m_WrappedCollection = wrappedCollection; m_TransformFunc = transform; ((INotifyCollectionChanged)m_WrappedCollection).CollectionChanged += TransformObservableCollection_CollectionChanged; m_TransformedCollection = new ObservableCollection(m_WrappedCollection.Select(m_TransformFunc)); } public void Dispose() { if (m_WrappedCollection == null) return; ((INotifyCollectionChanged)m_WrappedCollection).CollectionChanged -= TransformObservableCollection_CollectionChanged; m_WrappedCollection = null; } void TransformObservableCollection_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { switch (e.Action) { case NotifyCollectionChangedAction.Add: if (e.NewItems == null || e.NewItems.Count != 1) break; m_TransformedCollection.Insert(e.NewStartingIndex,m_TransformFunc((Source)e.NewItems[0])); return; case NotifyCollectionChangedAction.Move: if (e.NewItems == null || e.NewItems.Count != 1 || e.OldItems == null || e.OldItems.Count != 1) break; m_TransformedCollection.Move(e.OldStartingIndex, e.NewStartingIndex); return; case NotifyCollectionChangedAction.Remove: if (e.OldItems == null || e.OldItems.Count != 1) break; m_TransformedCollection.RemoveAt(e.OldStartingIndex); return; case NotifyCollectionChangedAction.Replace: if (e.NewItems == null || e.NewItems.Count != 1 || e.OldItems == null || e.OldItems.Count != 1 || e.OldStartingIndex != e.NewStartingIndex) break; m_TransformedCollection[e.OldStartingIndex] = m_TransformFunc((Source)e.NewItems[0]); return; } // This is most likely called on a Clear(), we don't optimize the other cases (yet) m_TransformedCollection.Clear(); foreach (var item in m_WrappedCollection) m_TransformedCollection.Add(m_TransformFunc(item)); } #region IList Edit functions that are unsupported because this collection is read only public int Add(object value) { throw new InvalidOperationException(); } public void Clear() { throw new InvalidOperationException(); } public void Insert(int index, object value) { throw new InvalidOperationException(); } public void Remove(object value) { throw new InvalidOperationException(); } public void RemoveAt(int index) { throw new InvalidOperationException(); } #endregion IList Edit functions that are unsupported because this collection is read only #region Accessors public T this[int index] { get { return m_TransformedCollection[index]; } } object IList.this[int index] { get { return m_TransformedCollection[index]; } set { throw new InvalidOperationException(); } } public bool Contains(T value) { return m_TransformedCollection.Contains(value); } bool IList.Contains(object value) { return ((IList)m_TransformedCollection).Contains(value); } public int IndexOf(T value) { return m_TransformedCollection.IndexOf(value); } int IList.IndexOf(object value) { return ((IList)m_TransformedCollection).IndexOf(value); } public int Count { get { return m_TransformedCollection.Count; } } public IEnumerator GetEnumerator() { return m_TransformedCollection.GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return ((IEnumerable)m_TransformedCollection).GetEnumerator(); } #endregion Accessors public bool IsFixedSize { get { return false; } } public bool IsReadOnly { get { return true; } } public void CopyTo(Array array, int index) { ((IList)m_TransformedCollection).CopyTo(array, index); } public void CopyTo(T[] array, int index) { m_TransformedCollection.CopyTo(array, index); } public bool IsSynchronized { get { return false; } } public object SyncRoot { get { return m_TransformedCollection; } } ObservableCollection m_TransformedCollection; ObservableCollection m_WrappedCollection; Func m_TransformFunc; event NotifyCollectionChangedEventHandler INotifyCollectionChanged.CollectionChanged { add { ((INotifyCollectionChanged)m_TransformedCollection).CollectionChanged += value; } remove { ((INotifyCollectionChanged)m_TransformedCollection).CollectionChanged -= value; } } } }