Como classifico uma coleção observável?


97

Eu tenho uma aula a seguir:

[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
}

Que coloquei em um 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"));

P: Como faço para classificar por chave?


Você está procurando uma implementação de classificação dentro da classe ou apenas qualquer tipo de classificação servirá?
okw

Não tenho certeza de como entender isso. Basicamente, eu só quero que ela seja organizada, a coleção não será muito grande (máximo de 20 itens), então qualquer coisa servirá (provavelmente)
Maciek

Veja isto para uma solução WPF stackoverflow.com/questions/1945461/…
Gayot Fow

Veja as respostas nesta página: indicação muito clara de uma API quebrada quando leva mais de 22 respostas para algumas funcionalidades básicas e críticas.
Gerry

Respostas:


19

Classificar um observável e retornar o mesmo objeto classificado pode ser feito usando um método de extensão. Para coleções maiores, esteja atento ao número de notificações alteradas na coleção.

Eu atualizei meu código para melhorar o desempenho e lidar com duplicatas (obrigado a nawfal por destacar o desempenho ruim do original, embora tenha funcionado bem no exemplo de dados original). O observável é particionado em uma metade classificada à esquerda e uma metade não classificada à direita, onde cada vez que o item mínimo (conforme encontrado na lista classificada) é deslocado para o final da partição classificada a partir do não classificado. Pior caso O (n). Essencialmente, uma classificação de seleção (veja a saída abaixo).

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
            }

uso: amostra com um observador (usei uma classe Person para mantê-lo simples)

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

Detalhes do andamento da classificação, mostrando como a coleção é dinamizada:

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,

A classe Person implementa IComparable e IEquatable; o último é usado para minimizar as mudanças na coleção de modo a reduzir o número de notificações de mudança levantadas

  • EDIT Classifica a mesma coleção sem criar uma nova cópia *

Para retornar um ObservableCollection, chame .ToObservableCollection em * SortOC * usando, por exemplo, [esta implementação] [1].

**** resposta original - isso cria uma nova coleção **** Você pode usar linq como o método doSort abaixo ilustra. Um rápido snippet de código: produz

3: xey 6: fty 7: aaa

Alternativamente, você pode usar um método de extensão na própria coleção

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

Achei isso e achei muito útil. É o LINQ que compõe a var classificada do OC?
Jason94,

9
Não sou fã dessa resposta porque ela não fornece uma ObservableCollection classificada.
xr280xr

63
-1, pois não classifica o ObservableCollection , mas cria uma nova coleção.
Kos

2
O código atualizado funcionará, mas tem complexidade de tempo O (n ^ 2). Isso pode ser melhorado para O (n * log (n)) usando em BinarySearchvez de IndexOf.
William Morrison de

2
Excelente solução! Para aqueles que herdam de ObservableCollection <T>, é possível usar o método protegido MoveItem () em vez de usar os métodos RemoveAt e Insert. Consulte também: referenceource.microsoft.com/#system/compmod/system/…
Herman Cordes

84

Esta extensão simples funcionou perfeitamente para mim. Eu só tinha que ter certeza de que MyObjectera IComparable. Quando o método de classificação é chamado na coleção observável de MyObjects, oCompareTo método em MyObjecté chamado, o que chama meu método de classificação lógica. Embora não tenha todos os sinos e assobios do resto das respostas postadas aqui, é exatamente o que eu precisava.

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

7
esta deve ser a resposta
thumbmunkeys

1
Atualizei minha resposta acima, pois foi a aceita e aborda a melhoria de desempenho em relação a esta resposta, que gera notificações de alteração para tudo na coleção
Andrew

3
Ótima resposta. Qualquer razão pela qual você usa em return Utils.LogicalStringCompare(a.Title, b.Title);vez dereturn string.Compare(a.Title, b.Title); ? @NeilW
Joe

2
@Joe, eu precisava fazer uma comparação lógica em vez de uma comparação de string padrão, e é por isso que precisei escrever a extensão em primeiro lugar. A comparação de strings lógicas classifica números em strings corretamente, em vez de ordená-los como strings (1, 2, 20, 1000 em vez de 1, 1000, 2, 20, etc.)
NielW

4
este é absolutamente o caminho a percorrer. Eu adicionei uma resposta minha estendendo isso, permitindo que você passe um keySelector em vez de usar IComparable, como o LINQ normalmente faz.
Jonesopolis

39

Encontrei uma entrada de blog relevante que fornece uma resposta melhor do que as aqui:

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

ATUALIZAR

O ObservableSortedList que @romkyns aponta nos comentários mantém a ordem de classificação automaticamente.

Implementa uma coleção observável que mantém seus itens em ordem de classificação. Em particular, as alterações nas propriedades do item que resultam em alterações na ordem são tratadas corretamente.

No entanto, observe também a observação

Pode haver bugs devido à complexidade comparativa da interface envolvida e sua documentação relativamente pobre (consulte https://stackoverflow.com/a/5883947/33080 ).


2
Na verdade, este blog é mais útil. No entanto, ainda estou para encontrar uma resposta decente para a questão de ter uma coleção observável que mantenha sua classificação à medida que os itens são adicionados e removidos. Vou escrever o meu próprio, acho.
Stephen Drew

@Steve Você pode tentar este .
Roman Starkov

Obrigado pelo link, optei pelo método de extensão, pois parecia a solução mais arrumada. Funciona muito bem: D
pengibot de

bw alguém percebeu que o blog tem um erro de digitação no nome do arquivo html (obversablecollection)? : P
laishiekai

1
@romkyns, a resposta foi estender ObservableCollection <T>. O GridView reconhece isso perfeitamente. Em seguida, apenas esconda seus métodos, assim como você faz. Vou postar uma solução completa quando tiver tempo.
Weston,

25

Você pode usar este método simples:

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

Você pode classificar assim:

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

Mais detalhes: http://jaider.net/2011-05-04/sort-a-observablecollection/


4
Isso limpa o ObservableCollection e, em seguida, adiciona novamente todos os objetos - portanto, é importante notar que se sua IU estiver vinculada à coleção, você não veria alterações animadas, por exemplo, quando os itens se movem
Carlos P

1
Não sei por que você tem que exibir itens em movimento ... por exemplo, você normalmente ObservableCollectionvincula a ItemSource dos menus suspensos e não vê a coleção de forma alguma. Além disso, esta operação de limpeza e enchimento é ultra rápida ... a "lenta" pode ser do tipo que já está otimizada. finalmente, você pode modificar este código para implementar seu método de movimento, tendo o sortedliste sourceo resto é fácil.
Jaider

3
Se você estiver vinculado a um menu suspenso, não se beneficiará em ver itens se movendo, é verdade. No entanto, se você estiver vinculado a uma ListBox, estruturas como WPF ou Silverlight ou Windows Store Apps fornecerão comentários visuais úteis à medida que os objetos da coleção são reindexados.
Carlos P

Embora seja mais rápido do que a abordagem Mover, isso gera uma série de eventos Redefinir / Adicionar. A resposta mais votada (abordagem Mover) minimiza isso e corretamente aumenta os Moveeventos, isso também apenas para os realmente movidos.
nawfal

19

O WPF fornece classificação ao vivo pronta para uso usando a ListCollectionViewclasse ...

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

Depois que a inicialização for concluída, não há mais nada a fazer. A vantagem sobre a classificação passiva é que ListCollectionView faz todo o trabalho pesado de uma forma transparente para o desenvolvedor. Novos itens são colocados automaticamente em sua ordem de classificação correta. Qualquer classe que derive IComparerde T é adequada para a propriedade de classificação personalizada.

Consulte ListCollectionView para obter a documentação e outros recursos.


6
que realmente funcionou: D essa é uma solução muito melhor do que a outra solução com "engenharia excessiva" para uma tarefa tão simples.
MushyPeas

Para onde foi o seu blog?
phoog de

O problema com as coisas "transparentes" é que você não consegue ver para onde olhar quando não funciona. A documentação da Microsoft tem um exemplo 100% transparente, ou seja, você não consegue ver de jeito nenhum.
Paul McCarthy

15

Eu gostei da abordagem do método de extensão de classificação por bolha no blog de "Richie" acima, mas não quero necessariamente apenas classificar comparando o objeto inteiro. Com mais frequência, desejo classificar por uma propriedade específica do objeto. Então, eu o modifiquei para aceitar um seletor de chave da maneira que OrderBy faz, para que você possa escolher em qual propriedade classificar:

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

Que você chamaria da mesma maneira que chamaria OrderBy, exceto que classificará a instância existente de sua ObservableCollection em vez de retornar uma nova coleção:

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

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

1
Obrigado por postar isso - conforme apontado nos comentários no blog de Richie, existem algumas melhorias valiosas neste código; em particular usando o método 'Mover' da fonte. Eu acho que isso substituiria as linhas Remover / Inserir com source.Move (j-1, j);
Carlos P

2
Este algoritmo de classificação não está otimizado en.wikipedia.org/wiki/Sorting_algorithm
Jaider

@Jaider Sim, é otimizado, não apenas para velocidade bruta geral.
jv42

Isso gera uma série de eventos Remover / Adicionar (para cada N que eu acredito). A resposta mais votada minimiza isso e corretamente aumenta os eventos Mover, isso também apenas para os realmente movidos. A chave aqui é não fazer uma classificação no local imediatamente, em vez disso classificá-la externamente usando OrderBye, em seguida, fazer uma comparação para descobrir a mudança real.
nawfal

11

A resposta de @NielW é o caminho a percorrer, para uma classificação real no local. Eu queria adicionar uma solução ligeiramente alterada que permite que você ignore a necessidade de usar 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);
    }
}

agora você pode chamá-lo como qualquer método LINQ:

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

2
Para um cookie de chocolate extra, você pode adicionar um parâmetro booleano "Ascending" e um if(!Ascending) sorted.Reverse();pouco antes de for: D (e não há necessidade de se preocupar mais com a memória, esse método Reverse não cria nenhum objeto novo, é o reverso no local)
Sharky,

De acordo com minha coleção de testes.Move (0,0) leva a um evento CollectionChanged. Portanto, seria uma melhoria de desempenho para verificar primeiro se um movimento é mesmo necessário.
sa.he

10

Eu gostaria de acrescentar à resposta de NeilW . Para incorporar um método que se assemelha ao encarregado do serviço. Adicione este método como uma extensão:

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

E use como:

myCollection = new ObservableCollection<MyObject>();

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

8

Uma variação é quando você classifica a coleção no local usando um algoritmo de classificação de seleção . Os elementos são movidos para o lugar usando o Movemétodo. Cada movimento irá disparar o CollectionChangedevento com NotifyCollectionChangedAction.Move(e também PropertyChangedcom o nome da propriedadeItem[] ).

Este algoritmo tem algumas propriedades interessantes:

  • O algoritmo pode ser implementado como uma classificação estável.
  • O número de itens movidos na coleção (por exemplo, CollectionChangedeventos disparados) é quase sempre menor do que outros algoritmos semelhantes, como classificação por inserção e classificação por bolha.

O algoritmo é bastante simples. A coleção é iterada para encontrar o menor elemento que é movido para o início da coleção. O processo é repetido começando no segundo elemento e assim por diante até que todos os elementos tenham sido movidos para o lugar. O algoritmo não é terrivelmente eficiente, mas para qualquer coisa que você vá exibir em uma interface de usuário, isso não importa. No entanto, em termos de número de operações de movimentação, é bastante eficiente.

Aqui está um método de extensão que, para simplificar, requer que os elementos sejam implementados IComparable<T>. Outras opções estão usando um IComparer<T>ou umFunc<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);
    }
  }

}

Classificar uma coleção é simplesmente uma questão de invocar o método de extensão:

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

1
Esta é a minha forma de classificação preferida de todas as descritas aqui, infelizmente o método Move não está disponível no Silverlight 5.
Eduardo Brites

1
Estou recebendo o erro 'Profiler.Profile.ProfileObject' não pode ser usado como parâmetro de tipo 'T' no tipo ou método genérico 'ObservableCollectionExtensions.Sort <T> (ObservableCollection <T>)'. Não há conversão de referência implícita de 'Profiler.Profile.ProfileObject' em 'System.IComparable <Profiler.Profile.ProfileObject>
New Bee

1
@NewBee: Este método de extensão especifica uma restrição genérica em Tser capaz de classificar os elementos na coleção. A classificação envolve o conceito de maior e menor e só você pode definir como ProfileObjecté ordenada. Para usar o método de extensão é necessário implementar IComparable<ProfileObject>no ProfileObject. Outras alternativas são as indicadas, especificando um IComparer<ProfileObject>ou a Func<ProfileObject, ProfileObject, int>e alterando o código de classificação de acordo.
Martin Liversage

4

Para melhorar um pouco o método de extensão na resposta xr280xr, adicionei um parâmetro bool opcional para determinar se a classificação é decrescente ou não. Também incluí a sugestão do Carlos P no comentário a essa resposta. Por favor veja abaixo.

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

2

Você precisa manter sua coleção organizada o tempo todo? Ao recuperar os pares, você precisa que eles estejam sempre classificados ou é apenas por algumas vezes (talvez apenas para apresentação)? Quão grande você espera que sua coleção seja? Existem muitos fatores que podem ajudá-lo a decidir o método de bruxa a ser usado.

Se você precisa que a coleção seja classificada o tempo todo, mesmo quando você insere ou deleta elementos e a velocidade de inserção não é um problema, talvez você deva implementar algum tipo de SortedObservableCollectioncomo @Gerrie Schenck mencionou ou verificar esta implementação .

Se você precisa que sua coleção seja classificada apenas algumas vezes, use:

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

Isso levará algum tempo para classificar a coleção, mas mesmo assim, pode ser a melhor solução dependendo do que você está fazendo com ela.


1
O link nesta resposta é para o código licenciado LGPL, portanto, se você for Silverlight (não consegue vincular dinamicamente) ou não é de código aberto, tenha cuidado com esse código.
yzorg

2

Minha resposta atual já tem mais votos, mas encontrei uma maneira melhor e mais moderna de fazer isso.

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

não seria melhor atualizar a resposta original?
Nathan Hughes

Não. Já teve mais votos positivos do que qualquer outra resposta. Não vou presumir que as pessoas prefiram fazer assim. Só pensei em oferecer outra maneira de fazer isso, especialmente porque havia uma abundância de novas respostas.
NielW

1

Faça uma nova classe SortedObservableCollection, derive-a ObservableCollectione implemente-a IComparable<Pair<ushort, string>>.


1

Uma maneira seria convertê-lo em uma lista e, em seguida, chamar Sort (), fornecendo um delegado de comparação. Algo como:-

(não testado)

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


1

Que diabos, vou dar uma resposta rapidamente remendada também ... parece um pouco com algumas outras implementações aqui, mas vou adicioná-la a qualquer um:

(mal testei, espero não estar me envergonhando)

Vamos estabelecer alguns objetivos primeiro (minhas suposições):

1) Deve classificar ObservableCollection<T> no local, para manter notificações, etc.

2) Não deve ser terrivelmente ineficiente (ou seja, algo próximo da eficiência de classificação "boa" padrão)

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

1

Nenhuma dessas respostas funcionou no meu caso. Ou porque atrapalha a ligação ou requer tantos códigos adicionais que é uma espécie de pesadelo, ou a resposta é simplesmente quebrada. Então, aqui está outra resposta mais simples que pensei. É muito menos código e permanece a mesma coleção observável com um tipo de método this.sort adicional. Deixe-me saber se há algum motivo pelo qual eu não deveria estar fazendo isso (eficiência, etc.).

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

... Onde ScoutItem é minha classe pública. Parecia muito mais simples. Benefício adicionado: ele realmente funciona e não mexe com ligações ou retorna uma nova coleção, etc.


1

Certo, como estava tendo problemas para fazer ObservableSortedList funcionar com o XAML, fui em frente e criei SortingObservableCollection . Ele herda de ObservableCollection, por isso funciona com XAML e testei a unidade com 98% de cobertura de código. Eu usei em meus próprios aplicativos, mas não prometo que é livre de bugs. Sinta-se à vontade para contribuir. Aqui está um exemplo de uso de código:

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

É um PCL, portanto, deve funcionar com Windows Store, Windows Phone e .NET 4.5.1.


1
Você provavelmente não deveria usar newem todos esses métodos, se alguém tiver uma instância de tipo mais genérico, esses métodos não serão chamados. Em overridevez disso, cada método substituível e altere-os conforme necessário ou faça fallback base.Method(...). Você, por exemplo, nem precisa se preocupar .Addporque isso usa internamente .InsertItem, então se .InsertItemfor sobrescrito e ajustado, .Addnão vai atrapalhar o pedido.
HB

1

Isso é o que eu faço com as extensões 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);
        }
    }

1

Isso funcionou para mim, encontrei há muito tempo em algum lugar.

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

Uso:

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

0

Eu precisava ser capaz de classificar por várias coisas, não apenas uma. Essa resposta é baseada em algumas das outras respostas, mas permite uma classificação mais complexa.

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

Ao usá-lo, faça uma série de chamadas OrderBy / ThenBy. Como isso:

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

0

Aprendi muito com as outras soluções, mas encontrei alguns problemas. Primeiro, alguns dependem do IndexOf, que tende a ser muito lento para listas grandes. Em segundo lugar, minha ObservableCollection tinha entidades EF e o uso de Remove parecia corromper algumas das propriedades de chave estrangeira. Talvez eu esteja fazendo algo errado.

Independentemente disso, A Move pode ser usado em vez de Remover / Inserir, mas isso causa alguns problemas com a correção de desempenho.

Para corrigir o problema de desempenho, crio um dicionário com os valores classificados IndexOf. Para manter o dicionário atualizado e preservar as propriedades da entidade, use uma troca implementada com dois movimentos em vez de um conforme implementado em outras soluções.

Um único movimento desloca os índices dos elementos entre os locais, o que invalidaria o dicionário IndexOf. Adicionar um segundo movimento para implementar uma troca restaura os locais.

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

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

Ah, entendi ... Gayot queria dar a recompensa pela resposta mais
rejeitada

Nunca vi recompensa premiada com sarcasmo :)
nawfal
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.