Sortierbare Spalten

Diesmal ein eher technischer Eintrag, dessen Zweck eher eigene Gedankenstütze als öffentliche Erheiterung ist. Hoffe ich zumindest.

Im .NET Framework 2.0 gibt es zusätzlich zum bekannten DataGrid Steuerelement das neue DataGridView. Dessen Fähigkeiten wurden gegenüber dem Vorgänger stark erweitert, so daß es inzwischen nur noch Sekunden dauert, um eine Datenquelle über ADO oder eine eigene Implementierung an die Tabelle anzuschließen. Das Ding bringt bereits nette Fähigkeiten mit, z.B. die Spalten umzuordnen. Die Spalten allerdings (durch einen Klick auf den Spaltenkopf) automatisch umzusortieren ist leider nicht so einfach, wie es zunächst aussieht. Das Sortieren der Datenquelle funktioniert nur dann problemlos, wenn ein Datagrid bzw. ein Dataview angebunden ist. Kommen die Daten direkt aus der Datenbank ist das auch kein Problem. Hat man jedoch die anzuzeigenden Objekte woanders her, z.B. aus einer Liste, funktioniert das leider nicht mehr. Die Liste weiß nicht, nach welchen Kriterien sie sortieren soll. Die Lösung besteht darin, eine eigene Listenimplementierung einzuführen, die von der ursprünglichen Liste erbt und zusätzlich IBindingList implementiert.

public void ApplySort(PropertyDescriptor property, ListSortDirection direction)
{
Trace.WriteLine(string.Format("sorting column {0}",property.DisplayName));
PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(typeof(AISShip));
this.sortProperty = properties.Find(property.DisplayName, true);
this.Sort(this.Comparison);
this.OnListChanged(resetEvent);
}

Diese Methode ist ein Teil der Implementierung von IBindingList. Sie wird beim Sortieren der Liste implizit (vom DataGridView durch ein callback) aufgerufen. Das spaltenrelevante Sortieren habe ich so gelöst, daß die entsprechende Spalte lokal gespeichert wird und die Suchfunktion mit einem Vergleichs-Delegaten aufgerufen wird. Das wirkt etwas ungewöhnlich, es ist auch eine Neuerung in .NET 2.0.
Die eigentliche Vergleichsfunktion sieht etwas lahmarschig aus, man hätte hier sicher noch eleganter mit Reflection arbeiten können (wenn auch weniger performant):

protected int Comparison(AISShip a, AISShip b)
{
if (this.sortProperty.Name.Equals("Timestamp"))
return a.Timestamp.CompareTo(b.Timestamp);
if (this.sortProperty.Name.Equals("Name"))
return a.Name.CompareTo(b.Name);
if (this.sortProperty.Name.Equals("Callsign"))
return a.Callsign.CompareTo(b.Callsign);
if (this.sortProperty.Name.Equals("IMO"))
return a.IMO.CompareTo(b.IMO);
// usw.
return 0;
}

Eine weitere Verbesserung kann man noch einbauen, wenn man sich beim Suchen außerdem die letzte Sortierrichtung merkt und diese dann umdreht, falls sich die Spalte nicht geändert hat. Damit erreicht man dann das typische Verhalten, daß der Benutzer aus dem Windows Explorer her kennt.

Leave a Reply