O que causa um WPF ListCollectionView que usa classificação personalizada para reorganizar seus itens?

StackOverflow https://stackoverflow.com/questions/601864

  •  03-07-2019
  •  | 
  •  

Pergunta

Considere este código (nomes de tipo genéricos para fins de exemplo):

// Bound to ListBox.ItemsSource
_items = new ObservableCollection<Item>();

// ...Items are added here ...

// Specify custom IComparer for this collection view
_itemsView = CollectionViewSource.GetDefaultView(_items)
((ListCollectionView)_itemsView).CustomSort = new ItemComparer();

Quando eu defini CustomSort, a coleção é classificada como eu espero.

No entanto, preciso que os dados se reortam em tempo de execução em resposta à mudança das propriedades em Item. o Item classe deriva de INotifyPropertyChanged E eu sei que a propriedade dispara corretamente quando meu modelo de dados atualiza os valores na tela, apenas a lógica de classificação não está sendo chamada.

Eu também tentei criar INotifyPropertyChanged.PropertyChanged Passando uma corda vazia, para ver se uma notificação genérica faria com que a classificação fosse iniciada. Sem bananas.

EDITAR Em resposta à sugestão de Kent, pensei em apontar que classificar os itens usando isso tem o mesmo resultado, a saber, que a coleção classifica uma vez, mas não Re-derrotar conforme os dados mudam:

_itemsView.SortDescriptions.Add(
    new SortDescription("PropertyName", ListSortDirection.Ascending));
Foi útil?

Solução

eu encontrei Este artigo do Dr. WPF que começa com uma resposta para minha pergunta e depois discutirá o impacto do desempenho da chamada Refresh. Alguns trechos importantes:

Infelizmente, o método refresh () resulta em uma regeneração completa da visualização. Quando um refresh () ocorre dentro da visualização, ele levanta uma notificação de coleta e fornece a ação como "redefinição". O itemContAineRerGenerator para o ListBox recebe essa notificação e responde descartando todos os visuais existentes para os itens. Em seguida, regenera completamente novos contêineres e visuais de itens.

Ele então descreve uma solução alternativa que melhora o desempenho. Em vez de chamar a atualização, remova, altere, depois re-adie o item.

Eu pensaria possível que a visualização da lista pudesse rastrear um item que muda e saiba reposicionar esse item sozinho dentro da visualização.

Uma nova abordagem foi introduzida em .NET 3.5 SP1 envolvendo a interface IEditableObject que fornece edição transacional por meio de ligações de dados ao modelo com métodos BeginEdit(), CancelEdit(), e EndEdit(). Ler o artigo Para maiores informações.

EDITAR Como User346528 aponta, IEditableObject Não era de fato novo em 3.5sp1. Na verdade, parece que está na estrutura Desde 1.0.

Outras dicas

Como a resposta aceita me direciona, sou capaz de forçar a reposição de itens únicos com código

IEditableCollectionView collectionView = DataGrid.Items;

collectionView.EditItem(changedItem);
collectionView.CommitEdit();

Onde changedItem é o Exibir modelo (o item no ItemsSource coleção).

Dessa forma, você não precisa de seus itens para implementar quaisquer interfaces como IEditableObject (que, na minha opinião, tem contrato muito complexo e difícil de implementar em alguns casos).

Batendo uma postagem antiga, mas basta fazer uma nova classe de coleção que herda da ListViewCollection e substitui o OnPropertyChanged (para um IbindingList, os eventos da ListChanged conterão a alteração da propriedade no parâmetro ListChangEDeventArgs). E verifique se os itens da coleção implementos e usam o InotifyPropertyChange sempre que uma propriedade altera (aumentada por você) ou a coleção não se vincular às alterações da propriedade.

Então, neste método onPropertyChanged, o remetente será o item. Remova o item se-e somente se-uma propriedade que causaria um resort for alterada, re-adicione-o (insira-o na posição classificada se adicionar isso já não o fizer). Mover um item é preferível se estiver disponível em vez de remover/adicionar. Da mesma forma, isso também deve ser feito com a filtragem (verificando o predicado do filtro).

IeditableObject não é necessário! Se você deseja editar várias propriedades, termine de edição (como editar 3 propriedades e selecionar uma linha diferente no WinForm DataGridView), então, classificando -o, esse seria o método adequado para fazer isso funcionar. Mas muitas vezes você provavelmente desejará que a coleção resista depois de alterar cada propriedade sem precisar chamar manualmente o Bedinedit/Endedit. O IeditableObject, btw, está presente na estrutura .NET 2.0 e não é novo no .NET 3.5 (se você ler o artigo do DR).

NOTA: Os problemas podem ocorrer usando o BEDINENDIT () e ENDEDIT () com várias edições para o mesmo item-a menos que você aumente (para verdadeiro)/decremento (para false) um número inteiro em vez de definir um booleano! Lembre -se de incrementar/diminuir um número inteiro para realmente saber quando a edição é concluída.

Manter uma lista perpetuamente classificada é demorada e propensa a erros (e pode quebrar o contrato de inserção se você forçar inserções classificadas) e só deve ser usado em certos locais, como combosbóxos. Em qualquer grade, essa é uma idéia muito ruim, pois a mudança de uma linha fará com que ela recorre a sob a posição atual dos usuários.

Public Class ListView
  Inherits ListCollectionView

  Protected Overrides Sub OnPropertyChanged(sender As Object, e As PropertyChangedEventArgs)

    ' Add resorting/filtering logic here.
  End Sub
End Class

O melhor exemplo em uma coleção que se faz semelhante ao que você precisa é o Jesse Johnsons ObjecjectView, embora este seja específico .NET 2.0 (ibindingList em vez de inotifyCollectionChanged/ObservableCollection/ListCollectionView) e usa uma licença muito restritiva. Seu blog ainda pode ser muito valioso em como ele conseguiu isso.

Editar:

Esqueci de acrescentar que o remetente será o item que você precisa para o recorrer, e e.PropertyName é o que você precisará usar para determinar se está dentro das descrições de SortDescripções. Caso contrário, uma alteração nessa propriedade não resultará em um resort necessário. Se o E.PropertyName não for nada, é como uma atualização, onde muitas propriedades podem ter mudado e a recorrer deve ser feita.

Para determinar se precisa ser filtrado, basta executá -lo através do FilterPredicate e removê -lo, se necessário. A filtragem é muito mais barata do que classificar.

Esperançosamente útil,

Tamusjroyce

Dado que você está usando a classificação personalizada, não há como o ListCollectionView saber quais critérios devem desencadear uma atualização. Portanto, você precisará ligar Refresh() Na coleção, você mesmo veja.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top