我有一个SortedObservableCollection
课程(最初基于this)。它完全符合它的承诺 - 它是一个泛型集合,它实现INotifyCollectionChanged
并按排序顺序维护其元素(根据提供的IComparer
)。订单仅在插入时检查 - 当插入项目时,它会被插入到集合中的正确位置。
但是我尝试使用这样的语法从XAML初始化集合时遇到了一个主要问题(Items
属性为SortedObservableCollection<MyItem>
类型,Priority
是排序键):< / p>
<my:SomeElement.Items>
<my:MyItem Priority="0">
<my:MyItem Priority="2">
<my:MyItem Priority="1">
</my:SomeElement.Items>
这应该导致使用按顺序2,1,0的项目进行收集,但结果是1,2,0。
我花了很长时间才发现原因:首先构建集合项,然后将其添加到集合中,然后才分配其属性值。
我无法在任何地方找到这种行为,我同意它通常并不重要。但在我的情况下,Priority
属性总是值为0,因此排序根本不会发生(实际上,项目的插入顺序与它们在XAML中的顺序相反)。在排序完成后,Priority
被初始化。
你自己遇到过这种行为吗?为什么XAML是这样实现的?我该如何解决这个问题?
我能想到的唯一解决方案是让项目实现INotifyPropertyChanged
然后在Add
方法中订阅它(然后在必要时更新订单),但我想这会带来比实际更麻烦(性能,内存泄漏......)。
感谢您的帮助!
答案 0 :(得分:1)
如果您的目标是一直在正确排序的集合,那么您将需要采用倾听方法。您可以使项目支持弱事件机制,以防止它们对集合进行强引用。
另一种方法是推迟排序,直到集合“完全构建”。例如,您可以在集合实现中使用标记isSorted
。每当修改集合时(为简单起见)都将此标志设置为false
,并在集合被“读取”之前进行检查。
这样的事情:
public void Add(T item)
{
_innerList.Add(item);
_isSorted = false;
}
和
public int IndexOf(T item)
{
EnsureSorted();
return _innerList.IndexOf(item);
}
其中EnsureSorted
看起来像这样:
private void EnsureSorted()
{
if (!_isSorted)
{
_innerList.Sort(_comparer);
_isSorted = true;
// TODO: Raise the CollectionChanged event here, specifying
// NotifyCollectionChangedAction.Reset
}
}
这样可以使您的集合显示排序,同时仍然允许在填充列表时将其排序。
也许这是一个可行的解决方法?
<强>更新强>
我使用这种延迟排序创建了一个简单的可观察集合。我想你可能会觉得它很有用,至少它应该清楚我的意思。
我们的想法是在“读取”集合之前调用EnsureSorted
方法,并在修改集合时清除isSorted
标志。
public class SortedObservableCollection<T> : IList<T>, IList, INotifyCollectionChanged, INotifyPropertyChanged
{
private readonly List<T> _innerList;
private IComparer<T> _comparer;
private bool _isSorted;
public event NotifyCollectionChangedEventHandler CollectionChanged;
public event PropertyChangedEventHandler PropertyChanged;
public SortedObservableCollection()
: this(null)
{
}
public SortedObservableCollection(IComparer<T> comparer)
{
_innerList = new List<T>();
_comparer = comparer ?? Comparer<T>.Default;
}
// Call this before "reading" collection
private void EnsureSorted()
{
if (!_isSorted)
{
_innerList.Sort(_comparer);
_isSorted = true;
}
}
// Call this after modifying the collection
private void NotifyChanged()
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("Count"));
}
if (CollectionChanged != null)
{
CollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
_isSorted = false;
}
#region List implementation
public int IndexOf(T item)
{
EnsureSorted();
return _innerList.IndexOf(item);
}
public void Insert(int index, T item)
{
EnsureSorted();
_innerList.Insert(index, item);
NotifyChanged();
}
public void RemoveAt(int index)
{
EnsureSorted();
_innerList.RemoveAt(index);
NotifyChanged();
}
public T this[int index]
{
get
{
EnsureSorted();
return _innerList[index];
}
set
{
EnsureSorted();
_innerList[index] = value;
NotifyChanged();
}
}
public void Add(T item)
{
_innerList.Add(item);
NotifyChanged();
}
public void Clear()
{
_innerList.Clear();
NotifyChanged();
}
public bool Contains(T item)
{
return _innerList.Contains(item);
}
public void CopyTo(T[] array, int arrayIndex)
{
EnsureSorted();
_innerList.CopyTo(array, arrayIndex);
}
public int Count
{
get { return _innerList.Count; }
}
public bool IsReadOnly
{
get { return false; }
}
public bool Remove(T item)
{
if (!_innerList.Remove(item))
{
return false;
}
NotifyChanged();
return true;
}
public IEnumerator<T> GetEnumerator()
{
EnsureSorted();
return _innerList.GetEnumerator();
}
#endregion
// Non-generic implementation omitted for brevity...
}