如何对可观察的集合进行排序?

Mac*_*iek 93 .net c# sorting wpf observablecollection

我有以下课程:

[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
}
Run Code Online (Sandbox Code Playgroud)

我把它放在一个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"));
Run Code Online (Sandbox Code Playgroud)

问:我如何按键排序?

Nie*_*elW 79

这个简单的扩展对我来说很漂亮.我只是要确保那MyObjectIComparable.在可观察集合上调用sort方法时,将调用on 方法MyObjects,该CompareTo方法MyObject调用我的Logical Sort方法.虽然这里没有其他答案的所有花里胡哨,但这正是我所需要的.

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();
Run Code Online (Sandbox Code Playgroud)

  • 这应该是答案 (6认同)
  • 这绝对是要走的路.我添加了自己的答案,扩展了这个,允许你传入一个keySelector,而不是像LINQ那样使用IComparable. (3认同)
  • 很好的答案.你使用`return Utils.LogicalStringCompare(a.Title,b.Title);`而不是`return string.Compare(a.Title,b.Title);`?@NeilW (2认同)
  • @Joe,我需要进行逻辑比较而不是标准字符串比较,这就是我首先需要编写扩展的原因。逻辑字符串比较正确地对字符串中的数字进行排序,而不是像字符串一样对它们进行排序(1, 2, 20, 1000 而不是 1, 1000, 2, 20 等) (2认同)

Eri*_* J. 38

我知道这个问题已经过时了,但是谷歌搜索时发现了一个相关的博客条目,提供了比这里更好的答案:

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

UPDATE

@romkyns在注释中指出的ObservableSortedList自动维护排序顺序.

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

但是请注意这句话

由于涉及的界面相对复杂,文档相对较差,可能会出错(参见/sf/answers/411876321/).

  • 实际上,这个博客更有用.但是,我还没有找到一个合适的答案,有一个可观察的集合,在添加和删除项目时保持其排序.我想我会写自己的. (2认同)

Jai*_*der 24

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

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);
}
Run Code Online (Sandbox Code Playgroud)

你可以像这样排序:

_collection.Sort(i => i.Key);
Run Code Online (Sandbox Code Playgroud)

更多细节:http://jaider.net/2011-05-04/sort-a-observablecollection/

  • 这将清除ObservableCollection,然后重新添加所有对象 - 因此值得注意的是,如果您的UI绑定到集合,您将看不到动画更改,例如当项目移动时 (4认同)
  • 如果您受到下拉限制,那么看到物品四处移动就不会受益,这是真的.如果您已经绑定到ListBox,那么诸如WPF或Silverlight或Windows Store Apps之类的框架将提供有用的视觉反馈,因为集合中的对象被重新编制索引. (2认同)

And*_*rew 20

OP编辑:正如许多人已正确指出原始答案不会返回相同的集合,(最初更侧重于排序Q的字典部分).请参阅底部的编辑,其中我解决了可观察集合的排序问题.原来离开这里仍然收到投票

您可以使用linq作为下面的doSort方法说明.一个快速的代码片段:产生

3:xey 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;
    }
}
Run Code Online (Sandbox Code Playgroud)

编辑

要返回ObservableCollection,请使用例如此实现sortedOC上调用.ToObservableCollection .

OP EDIT 可以使用扩展方法对可观察对象进行排序并返回相同的对象.对于较大的收藏品,请注意收集更改通知的数量,例如

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

        int ptr = 0;
        while (ptr < sorted.Count)
        {
            if (!observable[ptr].Equals(sorted[ptr]))
            {
                T t = observable[ptr];
                observable.RemoveAt(ptr);
                observable.Insert(sorted.IndexOf(t), t);
            }
            else
            {
                ptr++;
            }
        }
    }
Run Code Online (Sandbox Code Playgroud)

用法:使用观察者的示例(使用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 = "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 = 15 },
            new Person { Name = "Kim",  Age = 19 }
        };

        //what do observers see?
        observable.CollectionChanged += (o, e) => {

            if (e.OldItems != null)
            {
                foreach (var item in e.OldItems)
                {
                    Console.WriteLine("removed {0} at index {1}", item, e.OldStartingIndex);
                }
            }

            if (e.NewItems != null)
            {
                foreach (var item in e.NewItems)
                {
                    Console.WriteLine("added {0} at index {1}", item, e.NewStartingIndex);
                }
            }};            

        Console.WriteLine("\nsorting items...");
        observable.Sort();
    };
Run Code Online (Sandbox Code Playgroud)

上述输出:
删除Katy年龄51岁,指数0
加入Katy年龄51岁,指数8年
去除Mary年龄41岁,指数3
年级Mary年龄41岁,指数7年
删除Jane年龄20岁,指数3
添加Jane年龄20岁,指数5年
移除Jim年龄39岁在指数3处,
吉姆在指数6处追踪了39
岁.在指数4处,
简20岁的简添加了简20岁,在指数5处

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

  • -1因为它不对_the_`ObservableCollection`进行排序,而是创建一个新集合. (60认同)
  • 不是这个答案的粉丝,因为它没有给你一个排序的ObservableCollection. (9认同)
  • 更新后的代码可以工作,但时间复杂度为 O(n^2)。这可以通过使用 `BinarySearch` 而不是 `IndexOf` 改进为 O(n*log(n))。 (2认同)
  • 优秀解决方案 对于从ObservableCollection <T>继承的那些,可以使用受保护的MoveItem()方法而不是使用RemoveAt和Insert方法.另请参阅:http://referencesource.microsoft.com/#system/compmod/system/collections/objectmodel/observablecollection.cs,270a83d222656b02,references (2认同)
  • @HermanCordes现在有一个公共的`Move`方法 (2认同)

Gay*_*Fow 18

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

一旦完成初始化,就没有更多的事情可做了.被动排序的优势在于ListCollectionView以对开发人员透明的方式完成所有繁重工作.新项目将按正确的排序顺序自动放置.任何派生自IComparerT的类都适用于自定义排序属性.

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

  • 实际上有效:D这是一个比其他"过度工程"解决方案更好的解决方案,可以完成这么简单的任务. (5认同)

xr2*_*0xr 15

我喜欢上面"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);
                }
            }
        }
    }
Run Code Online (Sandbox Code Playgroud)

您可以使用与调用OrderBy相同的方式调用它,除非它将对ObservableCollection的现有实例进行排序,而不是返回新的集合:

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

people.Sort(p => p.FirstName);
Run Code Online (Sandbox Code Playgroud)

  • 此排序算法未经过优化http://en.wikipedia.org/wiki/Sorting_algorithm (2认同)

Jon*_*lis 9

对于真正的就地排序,@ 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);
    }
}
Run Code Online (Sandbox Code Playgroud)

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

myObservableCollection.Sort(o => o.MyProperty);
Run Code Online (Sandbox Code Playgroud)

  • 对于额外的巧克力饼干,您可以在 `for` :D 之前添加一个布尔参数“Ascending”和一个 `if(!Ascending) sorted.Reverse();`(并且无需进一步担心内存,即Reverse 方法不创建任何新对象,它是就地反向) (2认同)

Mar*_*age 8

您可以使用选择排序算法对集合进行排序.使用该Move方法将元素移动到位.每次移动都将CollectionChanged使用NotifyCollectionChangedAction.Move(以及PropertyChanged属性名称Item[])触发事件.

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

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

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

这是一种扩展方法,为简单起见,要求元素实现IComparable<T>.其他选项使用a IComparer<T>或a Func<T, T, Int32>.

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);
    }
  }

}
Run Code Online (Sandbox Code Playgroud)

对集合进行排序只需调用扩展方法:

var collection = new ObservableCollection<String>(...);
collection.Sort();
Run Code Online (Sandbox Code Playgroud)


DR.*_*DR. 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);
}
Run Code Online (Sandbox Code Playgroud)

并使用如下:

myCollection = new ObservableCollection<MyObject>();

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