【问题标题】:How do I sort an observable collection?如何对可观察的集合进行排序?
【发布时间】:2010-12-29 01:30:59
【问题描述】:

我有以下课程:

[DataContract]
public class Pair<TKey, TValue> : INotifyPropertyChanged, IDisposable
{
    public Pair(TKey key, TValue value)
    {
        Key = key;
        Value = value;
    }

    #region Properties
    [DataMember]
    public TKey Key
    {
        get
        { return m_key; }
        set
        {
            m_key = value;
            OnPropertyChanged("Key");
        }
    }
    [DataMember]
    public TValue Value
    {
        get { return m_value; }
        set
        {
            m_value = value;
            OnPropertyChanged("Value");
        }
    }
    #endregion

    #region Fields
    private TKey m_key;
    private TValue m_value;
    #endregion

    #region INotifyPropertyChanged Members

    public event PropertyChangedEventHandler PropertyChanged;

    protected void OnPropertyChanged(string name)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null)
        {
            handler(this, new PropertyChangedEventArgs(name));
        }
    }

    #endregion

    #region IDisposable Members

    public void Dispose()
    { }

    #endregion
}

我已经放入 ObservableCollection :

ObservableCollection<Pair<ushort, string>> my_collection = 
    new ObservableCollection<Pair<ushort, string>>();

my_collection.Add(new Pair(7, "aaa"));
my_collection.Add(new Pair(3, "xey"));
my_collection.Add(new Pair(6, "fty"));

问:如何按键排序?

【问题讨论】:

  • 您是在寻找类中的排序实现还是任何类型的排序都可以?
  • 不知道怎么理解。基本上我只想对其进行排序,集合不会很大(最多 20 个项目)所以任何事情都会做(很可能)
  • 查看这个以获得 WPF 解决方案stackoverflow.com/questions/1945461/…
  • 查看此页面上的答案:当某些关键和基本功能需要 22 多个答案时,非常清楚地表明 API 已损坏。

标签: c# .net wpf sorting observablecollection


【解决方案1】:

这个简单的扩展对我来说效果很好。我只需要确保MyObjectIComparable。当在MyObjects 的可观察集合上调用sort 方法时,会调用MyObject 上的CompareTo 方法,它调用我的逻辑排序方法。虽然它没有此处发布的其他答案的所有花里胡哨,但这正是我所需要的。

static class Extensions
{
    public static void Sort<T>(this ObservableCollection<T> collection) where T : IComparable
    {
        List<T> sorted = collection.OrderBy(x => x).ToList();
        for (int i = 0; i < sorted.Count(); i++)
            collection.Move(collection.IndexOf(sorted[i]), i);
    }
}

public class MyObject: IComparable
{
    public int CompareTo(object o)
    {
        MyObject a = this;
        MyObject b = (MyObject)o;
        return Utils.LogicalStringCompare(a.Title, b.Title);
    }

    public string Title;

}
  .
  .
  .
myCollection = new ObservableCollection<MyObject>();
//add stuff to collection
myCollection.Sort();

【讨论】:

  • 这似乎是对原始列表进行实际排序的唯一答案,并且没有删除/添加项目。
  • 更新了我上面的答案,因为它是被接受的答案,并解决了对这个答案的性能改进,这会引发集合中所有内容的更改通知
  • 很好的答案。有什么理由使用return Utils.LogicalStringCompare(a.Title, b.Title); 而不是return string.Compare(a.Title, b.Title);? @NeilW
  • @Joe,我需要进行逻辑比较而不是标准字符串比较,这就是我需要首先编写扩展的原因。逻辑字符串比较正确地对字符串中的数字进行排序,而不是像字符串一样对它们进行排序(1、2、20、1000 而不是 1、1000、2、20 等)
  • 这绝对是要走的路。我添加了一个我自己扩展的答案,允许你传入一个 keySelector 而不是使用 IComparable,就像 LINQ 通常做的那样。
【解决方案2】:

我找到了一个相关的博客文章,它提供了比这里更好的答案:

http://kiwigis.blogspot.com/2010/03/how-to-sort-obversablecollection.html

更新

@romkyns 在 cmets 中指出的 ObservableSortedList 自动维护排序顺序。

实现一个可观察的集合,它按排序顺序维护其项目。特别是,正确处理导致订单更改的项目属性更改。

但也要注意这句话

由于所涉及的接口相对复杂且文档相对较差(请参阅https://stackoverflow.com/a/5883947/33080),因此可能存在错误。

【讨论】:

  • 确实,这个博客更有用。但是,对于在添加和删除项目时保持其排序的可观察集合的问题,我还没有找到一个体面的答案。我想我会自己写。
  • @Steve 你可以试试this one
  • 感谢您的链接,我使用了扩展方法,因为这似乎是最整洁的解决方案。发挥魅力:D
  • bw 有人注意到博客的 html 文件名 (obversablecollection) 中有错字吗? :P
  • @romkyns 答案是扩展 ObservableCollection。 GridView 可以很好地识别它。然后就像你一样隐藏它的方法。有时间我会发布完整的解决方案。
【解决方案3】:

你可以使用这个简单的方法:

public static void Sort<TSource, TKey>(this Collection<TSource> source, Func<TSource, TKey> keySelector)
{
    List<TSource> sortedList = source.OrderBy(keySelector).ToList();
    source.Clear();
    foreach (var sortedItem in sortedList)
        source.Add(sortedItem);
}

你可以这样排序:

_collection.Sort(i => i.Key);

【讨论】:

  • 这会清除 ObservableCollection 然后重新添加所有对象 - 所以值得注意的是,如果您的 UI 绑定到集合,您将看不到动画更改,例如当物品移动时
  • 我不知道为什么你必须显示移动的项目......例如您通常将 ObservableCollection 绑定到下拉列表的 ItemSource 并且您根本看不到该集合。这种清除和填充的操作也非常快……“慢”可以是已经优化的那种。最后,您可以修改此代码以实现您的 move 方法,拥有 sortedlistsource 其余的很容易。
  • 如果你被绑定到一个下拉菜单,那么你不会从看到移动的项目中受益,这是真的。但是,如果您绑定到 ListBox,则 WPF 或 Silverlight 或 Windows 应用商店应用等框架将在重新索引集合中的对象时提供有用的视觉反馈。
  • 虽然这比移动方法更快,但这会引发许多重置/添加事件。投票最高的答案(移动方法)最小化了这一点并正确地引发了Move 事件,这也仅适用于真正移动的事件。
【解决方案4】:

可以使用扩展方法对 observable 进行排序并返回已排序的相同对象。对于较大的集合,请注意集合更改通知的数量。

我已更新我的代码以提高性能(感谢 nawfal)并处理在撰写本文时此处没有其他答案的重复项。 observable 被划分为左排序的一半和右未排序的一半,每次最小项(如在排序列表中找到的)从未排序的部分移动到排序分区的末尾。最坏情况 O(n)。本质上是一个选择排序(见下文输出)。

public static void Sort<T>(this ObservableCollection<T> collection)
        where T : IComparable<T>, IEquatable<T>
    {
        List<T> sorted = collection.OrderBy(x => x).ToList();

        int ptr = 0;
        while (ptr < sorted.Count - 1)
        {
            if (!collection[ptr].Equals(sorted[ptr]))
            {
                int idx = search(collection, ptr+1, sorted[ptr]);
                collection.Move(idx, ptr);
            }
            
            ptr++;
        }
    }

    public static int search<T>(ObservableCollection<T> collection, int startIndex, T other)
            {
                for (int i = startIndex; i < collection.Count; i++)
                {
                    if (other.Equals(collection[i]))
                        return i;
                }
    
                return -1; // decide how to handle error case
            }

用法: 带有观察者的示例(使用 Person 类保持简单)

    public class Person:IComparable<Person>,IEquatable<Person>
            { 
                public string Name { get; set; }
                public int Age { get; set; }
    
                public int CompareTo(Person other)
                {
                    if (this.Age == other.Age) return 0;
                    return this.Age.CompareTo(other.Age);
                }
    
                public override string ToString()
                {
                    return Name + " aged " + Age;
                }
    
                public bool Equals(Person other)
                {
                    if (this.Name.Equals(other.Name) && this.Age.Equals(other.Age)) return true;
                    return false;
                }
            }
    
          static void Main(string[] args)
            {
                Console.WriteLine("adding items...");
                var observable = new ObservableCollection<Person>()
                {
                    new Person {Name = "Katy", Age = 51},
                    new Person {Name = "Jack", Age = 12},
                    new Person {Name = "Bob", Age = 13},
                    new Person {Name = "Alice", Age = 39},
                    new Person {Name = "John", Age = 14},
                    new Person {Name = "Mary", Age = 41},
                    new Person {Name = "Jane", Age = 20},
                    new Person {Name = "Jim", Age = 39},
                    new Person {Name = "Sue", Age = 5},
                    new Person {Name = "Kim", Age = 19}
                };
    
                //what do observers see?
            
    
observable.CollectionChanged += (sender, e) =>
        {
            Console.WriteLine(
                e.OldItems[0] + " move from " + e.OldStartingIndex + " to " + e.NewStartingIndex);
            int i = 0;
            foreach (var person in sender as ObservableCollection<Person>)
            {
                if (i == e.NewStartingIndex)
                {
                    Console.Write("(" + (person as Person).Age + "),");
                }
                else
                {
                    Console.Write((person as Person).Age + ",");
                }
                
                i++;
            }

            Console.WriteLine();
        };

显示集合如何旋转的排序进度详细信息:

Sue aged 5 move from 8 to 0
(5),51,12,13,39,14,41,20,39,19,
Jack aged 12 move from 2 to 1
5,(12),51,13,39,14,41,20,39,19,
Bob aged 13 move from 3 to 2
5,12,(13),51,39,14,41,20,39,19,
John aged 14 move from 5 to 3
5,12,13,(14),51,39,41,20,39,19,
Kim aged 19 move from 9 to 4
5,12,13,14,(19),51,39,41,20,39,
Jane aged 20 move from 8 to 5
5,12,13,14,19,(20),51,39,41,39,
Alice aged 39 move from 7 to 6
5,12,13,14,19,20,(39),51,41,39,
Jim aged 39 move from 9 to 7
5,12,13,14,19,20,39,(39),51,41,
Mary aged 41 move from 9 to 8
5,12,13,14,19,20,39,39,(41),51,

Person 类同时实现了 IComparable 和 IEquatable,后者用于最小化对集合的更改,从而减少引发的更改通知的数量

  • EDIT 对同一集合进行排序而不创建新副本 *

要返回 ObservableCollection,请在 *sortedOC* 上调用 .ToObservableCollection,例如使用[此实现][1]。

**** 原始答案 - 这将创建一个新集合 **** 您可以使用 linq,如下所示的 doSort 方法。一个快速的代码 sn-p: 产生

3:xy 6:fty 7:aaa

或者,您可以在集合本身上使用扩展方法

var sortedOC = _collection.OrderBy(i => i.Key);

private void doSort()
{
    ObservableCollection<Pair<ushort, string>> _collection = 
        new ObservableCollection<Pair<ushort, string>>();

    _collection.Add(new Pair<ushort,string>(7,"aaa"));
    _collection.Add(new Pair<ushort, string>(3, "xey"));
    _collection.Add(new Pair<ushort, string>(6, "fty"));

    var sortedOC = from item in _collection
                   orderby item.Key
                   select item;

    foreach (var i in sortedOC)
    {
        Debug.WriteLine(i);
    }

}

public class Pair<TKey, TValue>
{
    private TKey _key;

    public TKey Key
    {
        get { return _key; }
        set { _key = value; }
    }
    private TValue _value;

    public TValue Value
    {
        get { return _value; }
        set { _value = value; }
    }
    
    public Pair(TKey key, TValue value)
    {
        _key = key;
        _value = value;

    }

    public override string ToString()
    {
        return this.Key + ":" + this.Value;
    }
}

【讨论】:

  • 找到了这个并觉得它最有帮助。是 LINQ 构成了 sortedOC 变量吗?
  • 不喜欢这个答案,因为它没有给你一个排序的 ObservableCollection。
  • -1 因为它不排序 the ObservableCollection,而是创建一个新集合。
  • 更新后的代码可以工作,但时间复杂度为 O(n^2)。这可以通过使用 BinarySearch 而不是 IndexOf 来改进为 O(n*log(n))。
  • 优秀的解决方案!对于从 ObservableCollection 继承的那些,可以使用受保护的 MoveItem() 方法而不是使用 RemoveAt 和 Insert 方法。另见:referencesource.microsoft.com/#system/compmod/system/…
【解决方案5】:

WPF 使用 ListCollectionView 类提供开箱即用的实时排序...

public ObservableCollection<string> MyStrings { get; set; }
private ListCollectionView _listCollectionView;
private void InitializeCollection()
{
    MyStrings = new ObservableCollection<string>();
    _listCollectionView = CollectionViewSource.GetDefaultView(MyStrings) 
              as ListCollectionView;
    if (_listCollectionView != null)
    {
        _listCollectionView.IsLiveSorting = true;
        _listCollectionView.CustomSort = new 
                CaseInsensitiveComparer(CultureInfo.InvariantCulture);
    }
}

一旦初始化完成,就没有什么可做的了。与被动排序相比的优势在于 ListCollectionView 以对开发人员透明的方式完成所有繁重的工作。新项目会自动按正确的排序顺序放置。任何派生自 T 的IComparer 的类都适用于自定义排序属性。

有关文档和其他功能,请参阅 ListCollectionView

【讨论】:

  • 实际工作 :D 对于这样一个简单的任务,这是一个比其他“过度设计”的解决方案更好的解决方案。
  • “透明”事物的问题在于,当它不起作用时,您看不到去哪里。微软的文档有一个 100% 透明的例子,也就是说你根本看不到。
【解决方案6】:

我喜欢上面“Richie”博客上的冒泡排序扩展方法方法,但我不一定只想对整个对象进行排序比较。我更经常想对对象的特定属性进行排序。所以我对其进行了修改,以像 OrderBy 那样接受一个键选择器,这样你就可以选择要排序的属性:

    public static void Sort<TSource, TKey>(this ObservableCollection<TSource> source, Func<TSource, TKey> keySelector)
    {
        if (source == null) return;

        Comparer<TKey> comparer = Comparer<TKey>.Default;

        for (int i = source.Count - 1; i >= 0; i--)
        {
            for (int j = 1; j <= i; j++)
            {
                TSource o1 = source[j - 1];
                TSource o2 = source[j];
                if (comparer.Compare(keySelector(o1), keySelector(o2)) > 0)
                {
                    source.Remove(o1);
                    source.Insert(j, o1);
                }
            }
        }
    }

除了它会对 ObservableCollection 的现有实例进行排序而不是返回新集合之外,您调用方式与调用 OrderBy 的方式相同:

ObservableCollection<Person> people = new ObservableCollection<Person>();
...

people.Sort(p => p.FirstName);

【讨论】:

  • 感谢您发布此内容 - 正如 Richie 博客上的 cmets 中所指出的,此代码有一些值得改进的地方;特别是使用源的“移动”方法。我想这会用 source.Move(j-1, j); 替换 Remove/Insert 行;
  • 此排序算法未优化en.wikipedia.org/wiki/Sorting_algorithm
  • @Jaider 是的,它已经过优化,只是不适用于整体原始速度。
  • 这引发了许多删除/添加事件(对于我相信的每个 N)。最高投票的答案最小化了这一点,并正确地引发了移动事件,这也仅适用于真正移动的事件。这里的关键是不要立即进行就地排序,而是使用OrderBy 在外部对其进行排序,然后进行比较以找出实际变化。
【解决方案7】:

@NielW 的答案是真正的就地排序。我想添加一个稍微改变的解决方案,让您不必使用IComparable

static class Extensions
{
    public static void Sort<TSource, TKey>(this ObservableCollection<TSource> collection, Func<TSource, TKey> keySelector)
    {
        List<TSource> sorted = collection.OrderBy(keySelector).ToList();
        for (int i = 0; i < sorted.Count(); i++)
            collection.Move(collection.IndexOf(sorted[i]), i);
    }
}

现在您可以像大多数 LINQ 方法一样调用它:

myObservableCollection.Sort(o => o.MyProperty);

【讨论】:

  • 对于额外的巧克力饼干,您可以在for 之前添加一个布尔参数“Ascending”和一个if(!Ascending) sorted.Reverse();:D(并且无需-进一步-担心内存,即 Reverse 方法不创建任何新对象,它是就地反向)
  • 根据我的测试,collection.Move(0,0) 导致 CollectionChanged 事件。因此,首先检查是否需要移动将是一种性能改进。
【解决方案8】:

我想添加到 NeilW 的答案中。合并一个类似于 orderby 的方法。将此方法添加为扩展:

public static void Sort<T>(this ObservableCollection<T> collection, Func<T,T> keySelector) where T : IComparable
{
    List<T> sorted = collection.OrderBy(keySelector).ToList();
    for (int i = 0; i < sorted.Count(); i++)
        collection.Move(collection.IndexOf(sorted[i]), i);
}

并像这样使用:

myCollection = new ObservableCollection<MyObject>();

//Sorts in place, on a specific Func<T,T>
myCollection.Sort(x => x.ID);

【讨论】:

    【解决方案9】:

    一种变体是您使用selection sort 算法对集合进行就地排序。使用Move 方法将元素移动到位。每一步都会触发带有NotifyCollectionChangedAction.MoveCollectionChanged 事件(以及带有属性名称Item[]PropertyChanged)。

    这个算法有一些很好的特性:

    • 该算法可以实现为稳定排序。
    • 集合中移动的项目数(例如CollectionChanged 触发的事件)几乎总是少于其他类似算法,如插入排序和冒泡排序。

    算法很简单。迭代集合以找到最小的元素,然后将其移动到集合的开头。从第二个元素开始重复该过程,依此类推,直到所有元素都移动到位。该算法效率不高,但对于您要在用户界面中显示的任何内容,它都无关紧要。但是,就移动操作的数量而言,它是非常有效的。

    这是一个扩展方法,为简单起见,要求元素实现IComparable&lt;T&gt;。其他选项使用IComparer&lt;T&gt;Func&lt;T, T, Int32&gt;

    public static class ObservableCollectionExtensions {
    
      public static void Sort<T>(this ObservableCollection<T> collection) where T : IComparable<T> {
        if (collection == null)
          throw new ArgumentNullException("collection");
    
        for (var startIndex = 0; startIndex < collection.Count - 1; startIndex += 1) {
          var indexOfSmallestItem = startIndex;
          for (var i = startIndex + 1; i < collection.Count; i += 1)
            if (collection[i].CompareTo(collection[indexOfSmallestItem]) < 0)
              indexOfSmallestItem = i;
          if (indexOfSmallestItem != startIndex)
            collection.Move(indexOfSmallestItem, startIndex);
        }
      }
    
    }
    

    对集合进行排序只是调用扩展方法的问题:

    var collection = new ObservableCollection<String>(...);
    collection.Sort();
    

    【讨论】:

    • 这是我在此处描述的所有排序方式中的首选排序方式,不幸的是,Silverlight 5 中不提供 Move 方法。
    • 我收到错误“Profiler.Profile.ProfileObject”不能用作泛型类型或方法“ObservableCollectionExtensions.Sort(ObservableCollection)”中的类型参数“T”。没有从 'Profiler.Profile.ProfileObject' 到 'System.IComparable 的隐式引用转换
    • @NewBee:此扩展方法在T 上指定generic constraint,以便能够对集合中的元素进行排序。排序涉及大于和小于的概念,只有您可以定义ProfileObject 的排序方式。要使用扩展方法,您需要在ProfileObject 上实现IComparable&lt;ProfileObject&gt;。其他替代方法如指定 IComparer&lt;ProfileObject&gt;Func&lt;ProfileObject, ProfileObject, int&gt; 并相应地更改排序代码。
    【解决方案10】:

    为了稍微改进 xr280xr 答案上的扩展方法,我添加了一个可选的 bool 参数来确定排序是否为降序。我还在对该答案的评论中包含了 Carlos P 提出的建议。请看下文。

    public static void Sort<TSource, TKey>(this ObservableCollection<TSource> source, Func<TSource, TKey> keySelector, bool desc = false)
        {
            if (source == null) return;
    
            Comparer<TKey> comparer = Comparer<TKey>.Default;
    
            for (int i = source.Count - 1; i >= 0; i--)
            {
                for (int j = 1; j <= i; j++)
                {
                    TSource o1 = source[j - 1];
                    TSource o2 = source[j];
                    int comparison = comparer.Compare(keySelector(o1), keySelector(o2));
                    if (desc && comparison < 0)
                        source.Move(j, j - 1);
                    else if (!desc && comparison > 0)
                        source.Move(j - 1, j);
                }
            }
        }
    

    【讨论】:

      【解决方案11】:

      您是否需要始终对您的收藏进行分类?检索对时,您是否需要始终对它们进行排序,或者只进行几次排序(可能只是为了呈现)?你希望你的收藏有多大?有很多因素可以帮助您决定要使用的女巫方法。

      如果您需要始终对集合进行排序,即使您插入或删除元素并且插入速度不是问题,也许您应该实现某种SortedObservableCollection 就像@Gerrie Schenck 提到的那样或查看this implementation .

      如果您需要对您的收藏品进行几次排序,请使用:

      my_collection.OrderBy(p => p.Key);
      

      这需要一些时间来对集合进行排序,但即便如此,这可能是最好的解决方案,具体取决于您使用它的方式。

      【讨论】:

      • 此答案中的链接指向 LGPL 许可代码,因此,如果您是 Silverlight(无法动态链接)或不开源,请谨慎使用该代码。
      【解决方案12】:

      我当前的答案已经获得了最多的选票,但我找到了一种更好、更现代的方法。

      class MyObject 
      {
            public int id { get; set; }
            public string title { get; set; }
      }
      
      ObservableCollection<MyObject> myCollection = new ObservableCollection<MyObject>();
      
      //add stuff to collection
      // .
      // .
      // .
      
      myCollection = new ObservableCollection<MyObject>(
          myCollection.OrderBy(n => n.title, Comparer<string>.Create(
          (x, y) => (Utils.Utils.LogicalStringCompare(x, y)))));
      

      【讨论】:

      • 更新原来的答案不是更好吗?
      • 没有。它已经比任何其他答案都得到了更多的支持。我不会假设人们宁愿这样做。只是想我会提供另一种方法,特别是因为有新答案的赏金。
      【解决方案13】:

      创建一个新类SortedObservableCollection,从ObservableCollection派生它并实现IComparable&lt;Pair&lt;ushort, string&gt;&gt;

      【讨论】:

        【解决方案14】:

        一种方法是将其转换为 List,然后调用 Sort(),提供比较委托。类似的东西:-

        (未经测试)

        my_collection.ToList().Sort((left, right) => left == right ? 0 : (left > right ? -1 : 1));
        

        【讨论】:

          【解决方案15】:

          这到底是怎么回事,我也会给出一个快速拼凑起来的答案......它看起来有点像这里的一些其他实现,但我会添加它任何人:

          (几乎没有经过测试,希望我不会让自己尴尬)

          让我们先陈述一些目标(我的假设):

          1) 必须对ObservableCollection&lt;T&gt; 进行排序,以维护通知等。

          2) 不能非常低效(即,接近标准的“良好”分拣效率)

          public static class Ext
          {
              public static void Sort<T>(this ObservableCollection<T> src)
                  where T : IComparable<T>
              {
                  // Some preliminary safety checks
                  if(src == null) throw new ArgumentNullException("src");
                  if(!src.Any()) return;
          
                  // N for the select,
                  // + ~ N log N, assuming "smart" sort implementation on the OrderBy
                  // Total: N log N + N (est)
                  var indexedPairs = src
                      .Select((item,i) => Tuple.Create(i, item))
                      .OrderBy(tup => tup.Item2);
                  // N for another select
                  var postIndexedPairs = indexedPairs
                      .Select((item,i) => Tuple.Create(i, item.Item1, item.Item2));
                  // N for a loop over every element
                  var pairEnum = postIndexedPairs.GetEnumerator();
                  pairEnum.MoveNext();
                  for(int idx = 0; idx < src.Count; idx++, pairEnum.MoveNext())
                  {
                      src.RemoveAt(pairEnum.Current.Item1);
                      src.Insert(idx, pairEnum.Current.Item3);            
                  }
                  // (very roughly) Estimated Complexity: 
                  // N log N + N + N + N
                  // == N log N + 3N
              }
          }
          

          【讨论】:

            【解决方案16】:

            这些答案都不适用于我的情况。要么是因为它搞砸了绑定,要么是因为它需要太多额外的编码以至于它是一场噩梦,或者答案只是被打破了。所以,这是我认为的另一个更简单的答案。它的代码少了很多,并且它仍然是相同的可观察集合,带有一个额外的 this.sort 类型的方法。如果有什么理由我不应该这样做(效率等),请告诉我?

            public class ScoutItems : ObservableCollection<ScoutItem>
            {
                public void Sort(SortDirection _sDir, string _sItem)
                {
                         //TODO: Add logic to look at _sItem and decide what property to sort on
                        IEnumerable<ScoutItem> si_enum = this.AsEnumerable();
            
                        if (_sDir == SortDirection.Ascending)
                        {
                            si_enum = si_enum.OrderBy(p => p.UPC).AsEnumerable();
                        } else
                        {
                            si_enum = si_enum.OrderByDescending(p => p.UPC).AsEnumerable();
                        }
            
                        foreach (ScoutItem si in si_enum)
                        {
                            int _OldIndex = this.IndexOf(si);
                            int _NewIndex = si_enum.ToList().IndexOf(si);
                            this.MoveItem(_OldIndex, _NewIndex);
                        }
                  }
            }
            

            ...ScoutItem 是我的公开课。只是看起来简单了很多。额外的好处:它确实有效,不会与绑定混淆或返回新集合等。

            【讨论】:

              【解决方案17】:

              好的,因为我在让 ObservableSortedList 与 XAML 一起工作时遇到了问题,所以我继续创建了 SortingObservableCollection。它继承自 ObservableCollection,因此它可以与 XAML 一起使用,并且我已经对其进行了 98% 的代码覆盖率的单元测试。我已经在自己的应用程序中使用过它,但我不会保证它没有错误。随意贡献。以下是示例代码用法:

              var collection = new SortingObservableCollection<MyViewModel, int>(Comparer<int>.Default, model => model.IntPropertyToSortOn);
              
              collection.Add(new MyViewModel(3));
              collection.Add(new MyViewModel(1));
              collection.Add(new MyViewModel(2));
              // At this point, the order is 1, 2, 3
              collection[0].IntPropertyToSortOn = 4; // As long as IntPropertyToSortOn uses INotifyPropertyChanged, this will cause the collection to resort correctly
              

              它是一个 PCL,因此它应该适用于 Windows Store、Windows Phone 和 .NET 4.5.1。

              【讨论】:

              • 您可能不应该在所有这些方法上使用new,如果有人有更通用类型的实例,则不会调用这些方法。而是 override 每个可覆盖的方法并根据需要更改它们或回退到 base.Method(...)。例如,您甚至不必担心.Add,因为它在内部使用.InsertItem,所以如果.InsertItem 被覆盖和调整,.Add 不会与排序混淆。
              【解决方案18】:

              这就是我对 OC 扩展所做的:

                  /// <summary>
                  /// Synches the collection items to the target collection items.
                  /// This does not observe sort order.
                  /// </summary>
                  /// <typeparam name="T"></typeparam>
                  /// <param name="source">The items.</param>
                  /// <param name="updatedCollection">The updated collection.</param>
                  public static void SynchCollection<T>(this IList<T> source, IEnumerable<T> updatedCollection)
                  {
                      // Evaluate
                      if (updatedCollection == null) return;
              
                      // Make a list
                      var collectionArray = updatedCollection.ToArray();
              
                      // Remove items from FilteredViewItems not in list
                      source.RemoveRange(source.Except(collectionArray));
              
                      // Add items not in FilteredViewItems that are in list
                      source.AddRange(collectionArray.Except(source));
                  }
              
                  /// <summary>
                  /// Synches the collection items to the target collection items.
                  /// </summary>
                  /// <typeparam name="T"></typeparam>
                  /// <param name="source">The source.</param>
                  /// <param name="updatedCollection">The updated collection.</param>
                  /// <param name="canSort">if set to <c>true</c> [can sort].</param>
                  public static void SynchCollection<T>(this ObservableCollection<T> source,
                      IList<T> updatedCollection, bool canSort = false)
                  {
                      // Synch collection
                      SynchCollection(source, updatedCollection.AsEnumerable());
              
                      // Sort collection
                      if (!canSort) return;
              
                      // Update indexes as needed
                      for (var i = 0; i < updatedCollection.Count; i++)
                      {
                          // Index of new location
                          var index = source.IndexOf(updatedCollection[i]);
                          if (index == i) continue;
              
                          // Move item to new index if it has changed.
                          source.Move(index, i);
                      }
                  }
              

              【讨论】:

                【解决方案19】:

                这对我有用,很久以前在某个地方找到的。

                // SortableObservableCollection
                public class SortableObservableCollection<T> : ObservableCollection<T>
                    {
                        public SortableObservableCollection(List<T> list)
                            : base(list)
                        {
                        }
                
                        public SortableObservableCollection()
                        {
                        }
                
                        public void Sort<TKey>(Func<T, TKey> keySelector, System.ComponentModel.ListSortDirection direction)
                        {
                            switch (direction)
                            {
                                case System.ComponentModel.ListSortDirection.Ascending:
                                    {
                                        ApplySort(Items.OrderBy(keySelector));
                                        break;
                                    }
                                case System.ComponentModel.ListSortDirection.Descending:
                                    {
                                        ApplySort(Items.OrderByDescending(keySelector));
                                        break;
                                    }
                            }
                        }
                
                        public void Sort<TKey>(Func<T, TKey> keySelector, IComparer<TKey> comparer)
                        {
                            ApplySort(Items.OrderBy(keySelector, comparer));
                        }
                
                        private void ApplySort(IEnumerable<T> sortedItems)
                        {
                            var sortedItemsList = sortedItems.ToList();
                
                            foreach (var item in sortedItemsList)
                            {
                                Move(IndexOf(item), sortedItemsList.IndexOf(item));
                            }
                        }
                    }
                

                用法:

                MySortableCollection.Sort(x => x, System.ComponentModel.ListSortDirection.Ascending);
                

                【讨论】:

                  【解决方案20】:

                  我需要能够按多种事物进行排序,而不仅仅是一项。此答案基于其他一些答案,但允许进行更复杂的排序。

                  static class Extensions
                  {
                      public static void Sort<T, TKey>(this ObservableCollection<T> collection, Func<ObservableCollection<T>, TKey> sort)
                      {
                          var sorted = (sort.Invoke(collection) as IOrderedEnumerable<T>).ToArray();
                          for (int i = 0; i < sorted.Count(); i++)
                              collection.Move(collection.IndexOf(sorted[i]), i);
                      }
                  }
                  

                  当你使用它时,传入一系列 OrderBy/ThenBy 调用。像这样:

                  Children.Sort(col => col.OrderByDescending(xx => xx.ItemType == "drive")
                                      .ThenByDescending(xx => xx.ItemType == "folder")
                                      .ThenBy(xx => xx.Path));
                  

                  【讨论】:

                    【解决方案21】:

                    我从其他解决方案中学到了很多,但我发现了一些问题。首先,有些依赖于 IndexOf,这对于大型列表来说往往很慢。其次,我的 ObservableCollection 有 EF 实体,使用 Remove 似乎破坏了一些外键属性。也许我做错了什么。

                    无论如何,可以使用移动来代替删除/插入,但这会导致性能修复出现一些问题。

                    为了解决性能问题,我使用 IndexOf 排序值创建了一个字典。要使字典保持最新并保留实体属性,请使用通过两次移动实现的交换,而不是其他解决方案中实现的一次移动。

                    一次移动会在位置之间移动元素的索引,这会使 IndexOf 字典无效。添加第二步以实施交换恢复位置。

                    public static void Sort<TSource, TKey>(this ObservableCollection<TSource> collection, Func<TSource, TKey> keySelector)
                    {
                        List<TSource> sorted = collection.OrderBy(keySelector).ToList();
                        Dictionary<TSource, int> indexOf = new Dictionary<TSource, int>();
                    
                        for (int i = 0; i < sorted.Count; i++)
                            indexOf[sorted[i]] = i;
                    
                        int idx = 0;
                        while (idx < sorted.Count)
                            if (!collection[idx].Equals(sorted[idx])) {
                                int newIdx = indexOf[collection[idx]]; // where should current item go?
                                collection.Move(newIdx, idx); // move whatever's there to current location
                                collection.Move(idx + 1, newIdx); // move current item to proper location
                            }
                            else {
                                idx++;
                            }
                    }
                    

                    【讨论】:

                      【解决方案22】:
                      var collection = new ObservableCollection<int>();
                      
                      collection.Add(7);
                      collection.Add(4);
                      collection.Add(12);
                      collection.Add(1);
                      collection.Add(20);
                      
                      // ascending
                      collection = new ObservableCollection<int>(collection.OrderBy(a => a));
                      
                      // descending
                      collection = new ObservableCollection<int>(collection.OrderByDescending(a => a));
                      

                      【讨论】:

                      • 哦,我明白了...Gayot 想把赏金奖励给投票最多的答案,哈哈
                      • 从未见过以讽刺的方式奖励赏金 :)
                      猜你喜欢
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 2023-03-31
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      相关资源
                      最近更新 更多