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 (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