Bereits seit Version 3.0 bietet WPF mit dem Interface ICollectionView die Möglichkeit, Daten sortiert anzeigen zu lassen. Allerdings wird die Sortierung nicht automatisch aktualisiert, wenn sich der Wert einer für die Sortierung relevanten Property ändert. Eine Aktualisierung findet nur statt, wenn ein neuer Datensatz hinzugefügt wird oder die Refresh-Methode des Interfaces aufgerufen wird. Ab .NET 4.5 steht das neue Interface ICollectionViewLiveShaping zur Verfügung, mit dem eine Live-Aktualisierung realisiert werden kann.

Für die folgenden Beispiele soll die folgende Klasse die darzustellenden Daten repräsentieren:

public class Person : INotifyPropertyChanged {

    private string firstName, lastName;

    public event PropertyChangedEventHandler PropertyChanged;

    public string FirstName{
        get {
            return this.firstName;
        }
        set {
            if (this.firstName != value) {
                this.firstName = value;
                this.OnPropertyChanged();
            }
        }
    }

    public string LastName{
        get {
            return this.lastName;
        }
        set {
            if (this.lastName != value) {
                this.lastName = value;
                this.OnPropertyChanged();
            }
        }
    }

    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) {
        PropertyChangedEventHandler handler = this.PropertyChanged;
            if (handler != null) {
                handler(this, new PropertyChangedEventArgs(propertyName));
            }
        }
}

Woher diese Daten kommen ist für das Thema irrelevant und wir konzentrieren uns darauf, diese Daten darzustellen:

namespace LiveShapingDemo {
    using System.Collections.ObjectModel;
    using System.ComponentModel;
    using System.Windows;
    using System.Windows.Data;

    public partial class MainWindow : Window {

        public MainWindow() {
            this.Persons = // Personenliste erzeugen
            ICollectionView collectionView = CollectionViewSource.GetDefaultView(this.Persons);
            collectionView.SortDescriptions.Add(new SortDescription("LastName", ListSortDirection.Ascending));

            this.InitializeComponent();
            this.DataContext = this;
        }

        public ObservableCollection<Person> Persons { get; set; }
    }
}

Im obigen Code wird über die Property Persons eine Liste von Personen bereitgestellt, die per Binding auf der Oberfläche angezeigt werden können. Mit Hilfe der Klasse CollectionViewSource wird die Default-View für diese Collection ermittelt und eine Sortierung nach dem Nachnamen festgelegt. Auf der Oberfläche werden also alle Personen sortiert nach dem Nachnamen angezeigt. Dieser Code ist seit .NET 3.0 möglich, verfügt jedoch über den weiter oben bereits genannten Nachteil, dass die Sortierung nicht aktualisiert wird, wenn sich der Nachname einer Person (z.B. von Meier nach Meyer) ändert. Um dies zu bewerkstelligen müsste nach einer Änderung des Nachnamens collectionView.Refresh() aufgerufen werden.

Mit Version 4.5 des .NET-Frameworks wurde das neue Interface ICollectionViewLiveShaping eingeführt, dass hier Abhilfe schaffen soll. Modifiziert man den obigen Code folgendermaßen, wird die Sortierung automatisch aktualisiert:

namespace LiveShapingDemo {
    using System.Collections.ObjectModel;
    using System.ComponentModel;
    using System.Windows;
    using System.Windows.Data;

    public partial class MainWindow : Window {

        public MainWindow() {
            this.Persons = // Personenliste erzeugen
            ICollectionView collectionView = CollectionViewSource.GetDefaultView(this.Persons);
            collectionView.SortDescriptions.Add(new SortDescription("LastName", ListSortDirection.Ascending));

            var collectionViewLiveShaping = collectionView as ICollectionViewLiveShaping;
            collectionViewLiveShaping.LiveSortingProperties.Add("LastName");
            collectionViewLiveShaping.IsLiveSorting = true;

            this.InitializeComponent();
            this.DataContext = this;
        }

        public ObservableCollection<Person> Persons { get; set; }
    }
}

Im mittleren Block des Konstruktors wird die Live-Sortierung konfiguriert. Im Wesentlichen geht es darum, die Properties festzulegen, welche für die Live-Sortierung berücksichtigt werden sollen und dann die Live-Sortierung zu aktivieren. Mit wenigen Zeilen Code lässt sich so eine automatische Sortierung realisieren.

Benötigt man diese Funktionalität öfter, kann man das Ganze mit Hilfe einer Extension-Methode automatisieren:

public static class LiveShapingExtensions {

    public static void EnableLiveSorting(this ICollectionView collectionView) {
        var liveShaping = collectionView as ICollectionViewLiveShaping;
        IEnumerable<string> sortProperties;

        if (collectionView == null) {
            throw new ArgumentNullException("collectionView");
        }

        if (liveShaping == null) {
            throw new ArgumentException("Cannot cast CollectionView to CollectionViewLiveShaping to enable live sorting.");
        }

        sortProperties = collectionView.SortDescriptions.Select(description => description.PropertyName);

        foreach (string property in sortProperties) {
            liveShaping.LiveSortingProperties.Add(property);
        }

        liveShaping.IsLiveSorting = true;
    }

}

Diese Extension-Methode berücksichtigt alle zur Sortierung verwendeten Properties und mit ihr kann die Initialisierung der Live-Sortierung auf die folgenden Zeilen reduziert werden:

namespace LiveShapingDemo {
    using System.Collections.ObjectModel;
    using System.ComponentModel;
    using System.Windows;
    using System.Windows.Data;

    public partial class MainWindow : Window {

        public MainWindow() {
            this.Persons = // Personenliste erzeugen
            ICollectionView collectionView = CollectionViewSource.GetDefaultView(this.Persons);
            collectionView.SortDescriptions.Add(new SortDescription("LastName", ListSortDirection.Ascending));

            collectionView.EnableLiveSorting();

            this.InitializeComponent();
            this.DataContext = this;
        }

        public ObservableCollection<Person> Persons { get; set; }
    }
}

Mit dem Interface ICollectionViewLiveShaping kann übrigens nicht nur zur eine Live-Sortierung, sondern auch eine Live-Gruppierung und Live-Filterung realisiert werden. Die Vorgehensweise dazu ist anlog und sollte einfach übertragen werden können.