O que causa um WPF ListCollectionView que usa classificação personalizada para reorganizar seus itens?
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));
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.