Qu'est-ce qui cause un ListCollectionView WPF qui utilise un tri personnalisé pour trier de nouveau ses éléments?

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

  •  03-07-2019
  •  | 
  •  

Question

Considérons ce code (noms de types génériques aux fins de l'exemple):

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

Lorsque je définis CustomSort , la collection est triée comme prévu.

Cependant, je demande aux données de se trier à nouveau au moment de l'exécution en réponse à la modification des propriétés de Item . La classe Item dérive de INotifyPropertyChanged et je sais que la propriété se déclenche correctement lorsque mon modèle de données met à jour les valeurs à l'écran, seule la logique de tri n'est pas appelée.

J'ai également essayé de lever INotifyPropertyChanged.PropertyChanged en transmettant une chaîne vide pour voir si une notification générique provoquerait l'initiation du tri. Pas de bananes.

MODIFIER En réponse à la suggestion de Kent, je pensais préciser que le tri des éléments à l'aide de cette méthode aboutit au même résultat, à savoir que la collection est triée une fois mais que ne le fait pas . re-trier lorsque les données changent:

_itemsView.SortDescriptions.Add(
    new SortDescription("PropertyName", ListSortDirection.Ascending));
Était-ce utile?

La solution

J'ai trouvé cet article de Dr .WPF qui commence par une réponse à ma question, puis aborde l’impact de l’appel Actualiser sur les performances. Quelques extraits clés:

  

Malheureusement, la méthode Refresh () entraîne une régénération complète de la vue. Lorsqu'une actualisation () se produit dans la vue, une notification CollectionChanged est émise et l'action est définie sous la forme "Réinitialisation". ItemContainerGenerator pour le contrôle ListBox reçoit cette notification et répond en supprimant tous les éléments visuels existants pour les éléments. Il régénère ensuite complètement les nouveaux conteneurs et éléments visuels.

Il décrit ensuite une solution de contournement qui améliore les performances. Plutôt que d'appeler Actualiser, supprimez, modifiez, puis rajoutez l'élément.

J'aurais pensé qu'il était possible que la vue liste puisse suivre un élément qui change et sache le repositionner seul dans la vue.

Une nouvelle approche a été introduite dans .NET 3.5 SP1 impliquant l’interface IEditableObject qui permet l’édition transactionnelle via des liaisons de données au modèle avec les méthodes BeginEdit () , CancelEdit () et EndEdit () . Lisez l'article pour plus d'informations.

MODIFIER comme indique l'utilisateur 346528 , IEditableObject n'était pas réellement nouveau dans la version 3.5SP1. En fait, il semble que cela ait été fait dans le cadre depuis 1.0 .

Autres conseils

Conformément à la réponse acceptée, je suis en mesure de forcer le repositionnement d'un élément à l'aide d'un code

IEditableCollectionView collectionView = DataGrid.Items;

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

modifiedItem est le modèle de vue (l'élément de la collection ItemsSource ).

De cette façon, vous n'avez pas besoin que vos éléments implémentent des interfaces telles que IEditableObject (qui, à mon avis, est très complexe et difficile à implémenter dans certains cas).

Jeter une ancienne publication, mais créer une nouvelle classe qui hérite de ListViewCollection et Overrides OnPropertyChanged (pour un IBindingList, les événements ListChanged contiendront la modification de propriété dans le paramètre ListChangedEventArgs). Et assurez-vous que les éléments de la collection implémentent et utilisent INotifyPropertyChange chaque fois qu'une propriété est modifiée (que vous avez soulevée), sinon la collection ne sera pas liée aux modifications de propriété.

Ensuite, dans cette méthode OnPropertyChanged, l'expéditeur sera l'élément. Supprimez l'élément si - et seulement si - une propriété qui causerait un recours est modifié, puis rajoutez-le (insérez-le à la position triée si l'ajouter ne le fait pas déjà). Le déplacement d'un élément est préférable s'il est disponible au lieu de le supprimer / l'ajouter. De même, cela devrait également être fait avec le filtrage (vérification du prédicat de filtrage).

IEditableObject n'est pas nécessaire! Si vous souhaitez éditer plusieurs propriétés, terminez l'édition (comme éditer 3 propriétés, puis en sélectionnant une autre ligne dans WinForm DataGridView), puis le trier serait la méthode appropriée pour que cela fonctionne. Mais souvent, vous voudrez probablement que la collection ait recours après avoir modifié chaque propriété sans avoir à appeler manuellement BeginEdit / EndEdit. IEditableObject, btw, est présent dans le framework .NET 2.0 et n'est pas nouveau dans .NET 3.5 (si vous lisez l'article de Dr).

Remarque: des problèmes peuvent survenir avec BeginEdit () et EndEdit () avec plusieurs modifications apportées au même élément, à moins que vous incrémentiez (pour true) / décrémentiez (pour false) un entier au lieu de définir une valeur booléenne! N'oubliez pas d'incrémenter / décrémenter un entier pour vraiment savoir quand l'édition est terminée.

Conserver une liste triée à perpétuité prend du temps et est source d'erreurs (cela peut rompre le contrat d'insertion si vous forcez les insertions triées) et ne devrait être utilisé qu'à certains endroits, tels que ComboBoxes. Quelle que soit la grille, c’est une très mauvaise idée, car le changement de ligne le fera sortir de la position actuelle de l’utilisateur.

Public Class ListView
  Inherits ListCollectionView

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

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

Jesse Johnsons ObjectListView est le meilleur exemple de collection qui correspond à ce dont vous avez besoin, bien qu’il soit spécifique à .NET 2.0 (IBindingList au lieu de INotifyCollectionChanged / ObservableCollection / ListCollectionView) et utilise une licence très restrictive. Son blog peut encore être très précieux dans la façon dont il a accompli cela.

Modifier:

Oublié d’ajouter que Sender sera l’élément à utiliser, et e.PropertyName vous permettra de déterminer s’il se trouve dans les SortDescriptions. Si ce n'est pas le cas, une modification de cette propriété ne fera pas en sorte qu'un complexe soit nécessaire. Si e.PropertyName vaut Nothing, il s’agit simplement d’une actualisation, dans laquelle de nombreuses propriétés ont peut-être changé et le recours doit être effectué.

Pour déterminer s'il doit être filtré, exécutez-le simplement via FilterPredicate et supprimez-le si nécessaire. Le filtrage coûte beaucoup moins cher que le tri.

J'espère utile,

TamusJRoyce

Etant donné que vous utilisez un tri personnalisé, ListCollectionView n'a aucun moyen de savoir quel critère doit déclencher une actualisation. Par conséquent, vous devrez appeler Refresh () sur la vue de collection vous-même.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top