2016-01-21 36 views
11

Wie können Sie in UWP-Apps eine ObservableCollection gruppieren und sortieren und die Live-Benachrichtigungsgüte beibehalten?UWP ObservableCollection Sortierung und Gruppierung

In den meisten einfachen UWP-Beispielen, die ich gesehen habe, gibt es im Allgemeinen ein ViewModel, das eine ObservableCollection verfügbar macht, die dann an eine ListView in der Ansicht gebunden ist. Wenn Elemente zur ObservableCollection hinzugefügt oder daraus entfernt werden, spiegelt das ListView die Änderungen automatisch wider, indem es auf die INotifyCollectionChanged-Benachrichtigungen reagiert. Dies funktioniert im Fall einer unsortierten oder nicht gruppierten ObservableCollection einwandfrei. Wenn die Auflistung jedoch sortiert oder gruppiert werden muss, scheint es keine offensichtliche Möglichkeit zu geben, die Updatebenachrichtigungen beizubehalten. Darüber hinaus scheint das Ändern der Sortier- oder Gruppenreihenfolge im laufenden Betrieb zu erheblichen Implementierungsproblemen zu führen.

++

ein Szenario, wo Sie ein vorhandenes Data Backend haben, die eine ObservableCollection von sehr einfachen Klasse Kontakt aussetzt.

public class Contact 
{ 
    public string FirstName { get; set; } 
    public string LastName { get; set; } 
    public string State { get; set; } 
} 

Diese ObservableCollection Veränderungen im Laufe der Zeit, und wir wollen eine Echtzeit präsentieren gruppiert und sortiert die Liste in der Ansicht, die als Reaktion auf Änderungen in der DataCache- aktualisiert. Wir möchten dem Benutzer auch die Möglichkeit geben, die Gruppierung zwischen LastName und State on the fly zu wechseln.

++

In einer WPF-Welt ist dies relativ trivial. Wir können ein einfaches ViewModel erstellen, das auf den Datencache verweist, der die Contacts-Auflistung des Caches unverändert darstellt.

public class WpfViewModel 
{ 
    public WpfViewModel() 
    { 
     _cache = GetCache(); 
    } 

    Cache _cache; 

    public ObservableCollection<Contact> Contacts 
    { 
     get { return _cache.Contacts; } 
    } 
} 

Dann können wir dies zu einer Ansicht binden, wo wir eine Collection implementieren und Sortier- und Gruppierungsdefinitionen als XAML-Ressourcen.

<Window ..... 
    xmlns:scm="clr-namespace:System.ComponentModel;assembly=WindowsBase"> 

    <Window.DataContext> 
     <local:WpfViewModel /> 
    </Window.DataContext> 

    <Window.Resources> 
     <CollectionViewSource x:Key="cvs" Source="{Binding Contacts}" /> 
     <PropertyGroupDescription x:Key="stategroup" PropertyName="State" /> 
     <PropertyGroupDescription x:Key="initialgroup" PropertyName="LastName[0]" /> 
     <scm:SortDescription x:Key="statesort" PropertyName="State" Direction="Ascending" /> 
     <scm:SortDescription x:Key="lastsort" PropertyName="LastName" Direction="Ascending" /> 
     <scm:SortDescription x:Key="firstsort" PropertyName="FirstName" Direction="Ascending" /> 
    </Window.Resources> 

    <Grid> 
     <Grid.RowDefinitions> 
      <RowDefinition Height="*" /> 
      <RowDefinition Height="Auto" /> 
     </Grid.RowDefinitions> 

     <ListView ItemsSource="{Binding Source={StaticResource cvs}}"> 
      <ListView.ItemTemplate> 
       <DataTemplate> 
        <Grid> 
         <Grid.ColumnDefinitions> 
          <ColumnDefinition Width="100" /> 
          <ColumnDefinition Width="100" /> 
          <ColumnDefinition Width="*" /> 
         </Grid.ColumnDefinitions> 
         <TextBlock Text="{Binding LastName}" /> 
         <TextBlock Text="{Binding FirstName}" Grid.Column="1" /> 
         <TextBlock Text="{Binding State}" Grid.Column="2" /> 
        </Grid> 
       </DataTemplate> 
      </ListView.ItemTemplate> 
      <ListView.GroupStyle> 
       <GroupStyle> 
        <GroupStyle.HeaderTemplate> 
         <DataTemplate> 
          <Grid Background="Gainsboro"> 
           <TextBlock FontWeight="Bold" 
              FontSize="14" 
              Margin="10,2" 
              Text="{Binding Name}"/> 
          </Grid> 
         </DataTemplate> 
        </GroupStyle.HeaderTemplate> 
       </GroupStyle> 
      </ListView.GroupStyle> 
     </ListView> 

     <StackPanel Orientation="Horizontal" Grid.Row="1"> 
      <Button Content="Group By Initial" Click="InitialGroupClick" /> 
      <Button Content="Group By State" Click="StateGroupClick" /> 
     </StackPanel> 

    </Grid> 
</Window> 

Dann, wenn der Benutzer klickt auf die GroupBy Schaltflächen am unteren Rand des Fensters können wir wir können Gruppe und Art on the fly in Code-Behind.

private void InitialGroupClick(object sender, RoutedEventArgs e) 
{ 
    var cvs = FindResource("cvs") as CollectionViewSource; 
    var initialGroup = (PropertyGroupDescription)FindResource("initialgroup"); 
    var firstSort = (SortDescription)FindResource("firstsort"); 
    var lastSort = (SortDescription)FindResource("lastsort"); 

    using (cvs.DeferRefresh()) 
    { 
     cvs.GroupDescriptions.Clear(); 
     cvs.SortDescriptions.Clear(); 
     cvs.GroupDescriptions.Add(initialGroup); 
     cvs.SortDescriptions.Add(lastSort); 
     cvs.SortDescriptions.Add(firstSort); 
    } 
} 

private void StateGroupClick(object sender, RoutedEventArgs e) 
{ 
    var cvs = FindResource("cvs") as CollectionViewSource; 
    var stateGroup = (PropertyGroupDescription)FindResource("stategroup"); 
    var stateSort = (SortDescription)FindResource("statesort"); 
    var lastSort = (SortDescription)FindResource("lastsort"); 
    var firstSort = (SortDescription)FindResource("firstsort"); 

    using (cvs.DeferRefresh()) 
    { 
     cvs.GroupDescriptions.Clear(); 
     cvs.SortDescriptions.Clear(); 
     cvs.GroupDescriptions.Add(stateGroup); 
     cvs.SortDescriptions.Add(stateSort); 
     cvs.SortDescriptions.Add(lastSort); 
     cvs.SortDescriptions.Add(firstSort); 
    } 
} 

Dies funktioniert alles gut, und die Elemente werden automatisch aktualisiert, wenn sich die Datencache-Sammlung ändert. Die Listenansichtsgruppierung und -auswahl bleibt von den Sammlungsänderungen unberührt, und die neuen Kontaktelemente sind korrekt gruppiert. Die Gruppierung kann zur Laufzeit zwischen Benutzerstatus und Nachname durch Benutzer ausgetauscht werden.

++

In der UWP Welt, die Collection hat nicht mehr die Groupdescriptions und SortDescriptions Sammlungen und Sortierung/Gruppierung muß auf der Ansichtsmodell Ebene durchgeführt werden.Die nächste Annäherung an eine praktikable Lösung ist entlang der Linien von Microsofts Beispielpaket

ich gefunden habe

https://github.com/Microsoft/Windows-universal-samples/tree/master/Samples/XamlListView

und diesen Artikel

http://motzcod.es/post/94643411707/enhancing-xamarinforms-listview-with-grouping

wo das Ansichtsmodell Gruppen die ObservableCollection mit Linq und präsentiert es der Ansicht als eine ObservableCollection von gruppierten Elementen

public ObservableCollection<GroupInfoList> GroupedContacts 
{ 
    ObservableCollection<GroupInfoList> groups = new ObservableCollection<GroupInfoList>(); 

    var query = from item in _cache.Contacts 
       group item by item.LastName[0] into g 
       orderby g.Key 
       select new { GroupName = g.Key, Items = g }; 

    foreach (var g in query) 
    { 
     GroupInfoList info = new GroupInfoList(); 
     info.Key = g.GroupName; 
     foreach (var item in g.Items) 
     { 
      info.Add(item); 
     } 
     groups.Add(info); 
    } 

    return groups; 
} 

wo GroupInfoList ist definiert als

public class GroupInfoList : List<object> 
{ 
    public object Key { get; set; } 
} 

Dies gilt zumindest bekommen wir eine gruppierte Sammlung in der Ansicht angezeigt, aber Aktualisierungen der DataCache- Sammlung sind nicht mehr in Echtzeit wider. Wir könnten das CollectionChanged-Ereignis des Datencaches erfassen und es im Viewmodel verwenden, um die GroupedContacts-Auflistung zu aktualisieren. Dadurch wird jedoch für jede Änderung im Datencache eine neue Auflistung erstellt, die ListView zum Flimmern und Zurücksetzen der Auswahl usw. veranlasst, was eindeutig suboptimal ist.

Auch das Austauschen der Gruppierung im laufenden Betrieb scheint eine vollständig separate ObservableCollection von gruppierten Elementen für jedes Gruppierungsszenario zu erfordern und die ItemSource-Bindung der ListView zur Laufzeit zu vertauschen.

Der Rest von dem, was ich habe extrem nützlich der UWP-Umgebung zu sehen scheint, also bin ich überrascht, um etwas so wichtig wie Gruppierung zu finden und Sortieren von Listen Hindernisse werfen ...

Wer weiß, wie zu tun das richtig?

+1

Warum nicht auf Ereignisse aus der zugrunde liegenden Sammlung hören und die gruppierte Sammlung entsprechend aktualisieren? Es kann etwas mehr Aufwand als Wiederaufbau erfordern, um herauszufinden, wo genau es hingestellt wird, aber ich würde mir vorstellen, dass das die CollectionViewSource getan hätte –

+0

Danke. ja, du hast Recht. Ich frage im Grunde, ob wir all das selbst tun müssen oder ob es eine bessere Alternative gibt, die die Macht der bestehenden ObservableCollection-Klasse nutzt. Es ist so eine Grundvoraussetzung, dass ich das Gefühl habe, den Klempnercode jedes Mal neu schreiben zu müssen, um das Rad neu zu erfinden, und dass die Plattform einen geeigneten Ansatz haben muss. Vielleicht nicht ... – Nick

Antwort

1

Best Effort bisher verwendet folgende Hilfsklasse ObservableGroupingCollection

public class ObservableGroupingCollection<K, T> where K : IComparable 
{ 
    public ObservableGroupingCollection(ObservableCollection<T> collection) 
    { 
     _rootCollection = collection; 
     _rootCollection.CollectionChanged += _rootCollection_CollectionChanged; 
    } 

    ObservableCollection<T> _rootCollection; 
    private void _rootCollection_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) 
    { 
     HandleCollectionChanged(e); 
    } 

    ObservableCollection<Grouping<K, T>> _items; 
    public ObservableCollection<Grouping<K, T>> Items 
    { 
     get { return _items; } 
    } 

    IComparer<T> _sortOrder; 
    Func<T, K> _groupFunction; 

    public void ArrangeItems(IComparer<T> sortorder, Func<T, K> group) 
    { 
     _sortOrder = sortorder; 
     _groupFunction = group; 

     var temp = _rootCollection 
      .OrderBy(i => i, _sortOrder) 
      .GroupBy(_groupFunction) 
      .ToList() 
      .Select(g => new Grouping<K, T>(g.Key, g)); 

     _items = new ObservableCollection<Grouping<K, T>>(temp); 

    } 

    private void HandleCollectionChanged(NotifyCollectionChangedEventArgs e) 
    { 
     if (e.Action == NotifyCollectionChangedAction.Add) 
     { 
      var item = (T)(e.NewItems[0]); 
      var value = _groupFunction.Invoke(item); 

      // find matching group if exists 
      var existingGroup = _items.FirstOrDefault(g => g.Key.Equals(value)); 

      if (existingGroup == null) 
      { 
       var newlist = new List<T>(); 
       newlist.Add(item); 

       // find first group where Key is greater than this key 
       var insertBefore = _items.FirstOrDefault(g => ((g.Key).CompareTo(value)) > 0); 
       if (insertBefore == null) 
       { 
        // not found - add new group to end of list 
        _items.Add(new Grouping<K, T>(value, newlist)); 
       } 
       else 
       { 
        // insert new group at this index 
        _items.Insert(_items.IndexOf(insertBefore), new Grouping<K, T>(value, newlist)); 
       } 
      } 
      else 
      { 
       // find index to insert new item in existing group 
       int index = existingGroup.ToList().BinarySearch(item, _sortOrder); 
       if (index < 0) 
       { 
        existingGroup.Insert(~index, item); 
       } 
      } 
     } 
     else if (e.Action == NotifyCollectionChangedAction.Remove) 
     { 
      var item = (T)(e.OldItems[0]); 
      var value = _groupFunction.Invoke(item); 

      var existingGroup = _items.FirstOrDefault(g => g.Key.Equals(value)); 

      if (existingGroup != null) 
      { 
       // find existing item and remove 
       var targetIndex = existingGroup.IndexOf(item); 
       existingGroup.RemoveAt(targetIndex); 

       // remove group if zero items 
       if (existingGroup.Count == 0) 
       { 
        _items.Remove(existingGroup); 
       } 
      } 
     } 

    } 
} 

wo die generische Gruppierung Klasse (die selbst eine ObservableCollection aussetzt) ​​aus diesem Artikel kommt

http://motzcod.es/post/94643411707/enhancing-xamarinforms-listview-with-grouping

zu arbeiten Demo zu machen: -

Fügen Sie die obige ObservableGroupingCollection aus der neuen UWP Blank-Anwendung hinzu Klasse. Dann fügen Sie eine weitere Klassendatei in demselben Namespace und fügen Sie alle folgenden Klassen

// Data models 

public class Contact 
{ 
    public string FirstName { get; set; } 
    public string LastName { get; set; } 
    public string State { get; set; } 
} 

public class DataPool 
{ 
    public static string GenerateFirstName(Random random) 
    { 
     List<string> names = new List<string>() { "Lilly", "Mukhtar", "Sophie", "Femke", "Abdul-Rafi", "Mariana", "Aarif", "Sara", "Ibadah", "Fakhr", "Ilene", "Sardar", "Hanna", "Julie", "Iain", "Natalia", "Henrik", "Rasa", "Quentin", "Gadi", "Pernille", "Ishtar", "Jimmy", "Justine", "Lale", "Elize", "Randy", "Roshanara", "Rajab", "Marcus", "Mark", "Alima", "Francisco", "Thaqib", "Andreas", "Marianna", "Amalie", "Rodney", "Dena", "Amar", "Anna", "Nasreen", "Reema", "Tomas", "Filipa", "Frank", "Bari'ah", "Parvaiz", "Jibran", "Tomas", "Elli", "Carlos", "Diego", "Henrik", "Aruna", "Vahid", "Eliana", "Roxanne", "Amanda", "Ingrid", "Wesley", "Malika", "Basim", "Eisa", "Alina", "Andreas", "Deeba", "Diya", "Parveen", "Bakr", "Celine", "Daniel", "Mattheus", "Edmee", "Hedda", "Maria", "Maja", "Alhasan", "Alina", "Hedda", "Vanja", "Robin", "Victor", "Aaftab", "Guilherme", "Maria", "Kai", "Sabien", "Abdel", "Jason", "Bahaar", "Vasco", "Jibran", "Parsa", "Catalina", "Fouad", "Colette", "John", "Fred", "James", "Harry", "Ben", "Steven", "Philip", "Dougal", "Jasper", "Elliott", "Charles", "Gerty", "Sarah", "Sonya", "Svetlana", "Dita", "Karen", "Christine", "Angela", "Heather", "Spence", "Graham", "David", "Bernie", "Darren", "Lester", "Vince", "Colin", "Bernhard", "Dieter", "Norman", "William", "Nigel", "Nick", "Nikki", "Trent", "Devon", "Steven", "Eric", "Derek", "Raymond", "Craig" }; 
     return names[random.Next(0, names.Count)]; 
    } 
    public static string GenerateLastName(Random random) 
    { 
     List<string> lastnames = new List<string>() { "Carlson", "Attia", "Quincey", "Hollenberg", "Khoury", "Araujo", "Hakimi", "Seegers", "Abadi", "Krommenhoek", "Siavashi", "Kvistad", "Vanderslik", "Fernandes", "Dehmas", "Sheibani", "Laamers", "Batlouni", "Lyngvær", "Oveisi", "Veenhuizen", "Gardenier", "Siavashi", "Mutlu", "Karzai", "Mousavi", "Natsheh", "Nevland", "Lægreid", "Bishara", "Cunha", "Hotaki", "Kyvik", "Cardoso", "Pilskog", "Pennekamp", "Nuijten", "Bettar", "Borsboom", "Skistad", "Asef", "Sayegh", "Sousa", "Miyamoto", "Medeiros", "Kregel", "Shamoun", "Behzadi", "Kuzbari", "Ferreira", "Barros", "Fernandes", "Xuan", "Formosa", "Nolette", "Shahrestaani", "Correla", "Amiri", "Sousa", "Fretheim", "Van", "Hamade", "Baba", "Mustafa", "Bishara", "Formo", "Hemmati", "Nader", "Hatami", "Natsheh", "Langen", "Maloof", "Patel", "Berger", "Ostrem", "Bardsen", "Kramer", "Bekken", "Salcedo", "Holter", "Nader", "Bettar", "Georgsen", "Cuninho", "Zardooz", "Araujo", "Batalha", "Antunes", "Vanderhoorn", "Srivastava", "Trotter", "Siavashi", "Montes", "Sherzai", "Vanderschans", "Neves", "Sarraf", "Kuiters", "Hestoe", "Cornwall", "Paisley", "Cooper", "Jakoby", "Smith", "Davies", "Jonas", "Bowers", "Fernandez", "Perez", "Black", "White", "Keller", "Hernandes", "Clinton", "Merryweather", "Freeman", "Anguillar", "Goodman", "Hardcastle", "Emmott", "Kirkby", "Thatcher", "Jamieson", "Spender", "Harte", "Pinkman", "Winterman", "Knight", "Taylor", "Wentworth", "Manners", "Walker", "McPherson", "Elder", "McDonald", "Macintosh", "Decker", "Takahashi", "Wagoner" }; 
     return lastnames[random.Next(0, lastnames.Count)]; 
    } 
    public static string GenerateState(Random random) 
    { 
     List<string> states = new List<string>() { "Alabama", "Alaska", "Arizona", "Arkansas", "California", "Colorado", "Connecticut", "Delaware", "District Of Columbia", "Florida", "Georgia", "Hawaii", "Idaho", "Illinois", "Indiana", "Iowa", "Kansas", "Kentucky", "Louisiana", "Maine", "Maryland", "Massachusetts", "Michigan", "Minnesota", "Mississippi", "Missouri", "Montana", "Nebraska", "Nevada", "New Hampshire", "New Jersey", "New Mexico", "New York", "North Carolina", "North Dakota", "Ohio", "Oklahoma", "Oregon", "Pennsylvania", "Rhode Island", "South Carolina", "South Dakota", "Tennessee", "Texas", "Utah", "Vermont", "Virginia", "Washington", "West Virginia", "Wisconsin", "Wyoming" }; 
     return states[random.Next(0, states.Count)]; 
    } 
} 

public class Cache 
{ 
    public Cache() 
    { 
     InitializeCacheData(); 
     SimulateLiveChanges(new TimeSpan(0, 0, 1)); 
    } 

    public ObservableCollection<Contact> Contacts { get; set; } 

    private static Random rnd = new Random(); 

    private void InitializeCacheData() 
    { 
     Contacts = new ObservableCollection<Contact>(); 

     var i = 0; 
     while (i < 5) 
     { 
      Contacts.Add(new Contact() 
      { 
       FirstName = DataPool.GenerateFirstName(rnd), 
       LastName = DataPool.GenerateLastName(rnd), 
       State = DataPool.GenerateState(rnd) 
      }); 

      i++; 
     } 
    } 

    private async void SimulateLiveChanges(TimeSpan MyInterval) 
    { 
     double MyIntervalSeconds = MyInterval.TotalSeconds; 
     while (true) 
     { 
      await Task.Delay(MyInterval); 

      //int addOrRemove = rnd.Next(1, 10); 
      //if (addOrRemove > 3) 
      //{ 
      // add item 
      Contacts.Add(new Contact() 
      { 
       FirstName = DataPool.GenerateFirstName(rnd), 
       LastName = DataPool.GenerateLastName(rnd), 
       State = DataPool.GenerateState(rnd) 
      }); 
      //} 
      //else 
      //{ 
      // // remove random item 
      // if (Contacts.Count > 0) 
      // { 
      //  Contacts.RemoveAt(rnd.Next(0, Contacts.Count - 1)); 
      // } 
      //} 
     } 
    } 

} 

// ViewModel 

public class ViewModel : BaseViewModel 
{  
    public ViewModel() 
    { 
     _groupingCollection = new ObservableGroupingCollection<string, Contact>(new Cache().Contacts); 
     _groupingCollection.ArrangeItems(new StateSorter(), (x => x.State)); 
     NotifyPropertyChanged("GroupedContacts"); 

    } 

    ObservableGroupingCollection<string, Contact> _groupingCollection; 
    public ObservableCollection<Grouping<string, Contact>> GroupedContacts 
    { 
     get 
     { 
      return _groupingCollection.Items; 
     } 
    } 

    // swap grouping commands 

    private ICommand _groupByStateCommand; 
    public ICommand GroupByStateCommand 
    { 
     get 
     { 
      if (_groupByStateCommand == null) 
      { 
       _groupByStateCommand = new RelayCommand(
        param => GroupByState(), 
        param => true); 
      } 
      return _groupByStateCommand; 
     } 
    } 
    private void GroupByState() 
    { 
     _groupingCollection.ArrangeItems(new StateSorter(), (x => x.State)); 
     NotifyPropertyChanged("GroupedContacts"); 
    } 

    private ICommand _groupByNameCommand; 
    public ICommand GroupByNameCommand 
    { 
     get 
     { 
      if (_groupByNameCommand == null) 
      { 
       _groupByNameCommand = new RelayCommand(
        param => GroupByName(), 
        param => true); 
      } 
      return _groupByNameCommand; 
     } 
    } 
    private void GroupByName() 
    { 
     _groupingCollection.ArrangeItems(new NameSorter(), (x => x.LastName.First().ToString())); 
     NotifyPropertyChanged("GroupedContacts"); 
    } 

} 

// View Model helpers 

public class BaseViewModel : INotifyPropertyChanged 
{ 
    public event PropertyChangedEventHandler PropertyChanged; 

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

public class RelayCommand : ICommand 
{ 
    readonly Action<object> _execute; 
    readonly Predicate<object> _canExecute; 

    public RelayCommand(Action<object> execute) 
     : this(execute, null) 
    { 

    } 

    public RelayCommand(Action<object> execute, Predicate<object> canExecute) 
    { 
     if (execute == null) 
      throw new ArgumentNullException("execute"); 

     _execute = execute; 
     _canExecute = canExecute; 

    } 

    public bool CanExecute(object parameter) 
    { 
     return _canExecute == null ? true : _canExecute(parameter); 
    } 

    public event EventHandler CanExecuteChanged 
    { 
     add { } 
     remove { } 
    } 

    public void Execute(object parameter) 
    { 
     _execute(parameter); 
    } 

} 

// Sorter classes 

public class NameSorter : Comparer<Contact> 
{ 
    public override int Compare(Contact x, Contact y) 
    { 
     int result = x.LastName.First().CompareTo(y.LastName.First()); 

     if (result != 0) 
     { 
      return result; 
     } 
     else 
     { 
      result = x.LastName.CompareTo(y.LastName); 

      if (result != 0) 
      { 
       return result; 
      } 
      else 
      { 
       return x.FirstName.CompareTo(y.FirstName); 
      } 
     } 
    } 
} 

public class StateSorter : Comparer<Contact> 
{ 
    public override int Compare(Contact x, Contact y) 
    { 
     int result = x.State.CompareTo(y.State); 

     if (result != 0) 
     { 
      return result; 
     } 
     else 
     { 
      result = x.LastName.CompareTo(y.LastName); 

      if (result != 0) 
      { 
       return result; 
      } 
      else 
      { 
       return x.FirstName.CompareTo(y.FirstName); 
      } 
     } 
    } 
} 

// Grouping class 
// credit 
// http://motzcod.es/post/94643411707/enhancing-xamarinforms-listview-with-grouping 

public class Grouping<K, T> : ObservableCollection<T> 
{ 
    public K Key { get; private set; } 

    public Grouping(K key, IEnumerable<T> items) 
    { 
     Key = key; 
     foreach (var item in items) 
     { 
      this.Items.Add(item); 
     } 
    } 
} 

Schließlich bearbeiten Mainpage wie folgt

<Page.DataContext> 
     <local:ViewModel /> 
    </Page.DataContext> 

    <Page.Resources> 
     <CollectionViewSource 
      x:Key="cvs" 
      Source="{Binding GroupedContacts}" 
      IsSourceGrouped="True" /> 
    </Page.Resources> 

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> 

     <Grid.RowDefinitions> 
      <RowDefinition Height="*" /> 
      <RowDefinition Height="Auto" /> 
     </Grid.RowDefinitions> 

     <ListView ItemsSource="{Binding Source={StaticResource cvs}}" 
        x:Name="targetListBox"> 
      <ListView.ItemTemplate> 
       <DataTemplate> 
        <Grid> 
         <Grid.ColumnDefinitions> 
          <ColumnDefinition Width="100" /> 
          <ColumnDefinition Width="100" /> 
          <ColumnDefinition Width="*" /> 
         </Grid.ColumnDefinitions> 

         <TextBlock Text="{Binding LastName}" /> 
         <TextBlock Text="{Binding FirstName}" Grid.Column="1" /> 
         <TextBlock Text="{Binding State}" Grid.Column="2" HorizontalAlignment="Right" /> 
        </Grid> 
       </DataTemplate> 
      </ListView.ItemTemplate> 
      <ListView.GroupStyle> 
       <GroupStyle> 
        <GroupStyle.HeaderTemplate> 
         <DataTemplate> 
          <Grid Background="Gainsboro"> 
           <TextBlock FontWeight="Bold" 
              FontSize="14" 
              Margin="10,2" 
              Text="{Binding Key}"/> 
          </Grid> 
         </DataTemplate> 
        </GroupStyle.HeaderTemplate> 
       </GroupStyle> 
      </ListView.GroupStyle> 
     </ListView> 

     <StackPanel Orientation="Horizontal" Grid.Row="1"> 
      <Button Content="Group By Initial" Command="{Binding GroupByNameCommand}" /> 
      <Button Content="Group By State" Command="{Binding GroupByStateCommand}" /> 
     </StackPanel> 
    </Grid> 

HandleCollectionChanged Methode nur Griffe hinzufügen/entfernen, so weit und wird zusammenbrechen, wenn NotifyCollectionChangedEventArgs Parameter enthält mehrere Elemente (die vorhandene ObservableCollection-Klasse benachrichtigt Änderungen nur einzeln)

So funktioniert es in Ordnung, aber es fühlt sich alles irgendwie hacky.

Vorschläge für Verbesserungen sehr willkommen.

5

Ich habe angefangen, eine Bibliothek namens GroupedObservableCollection zusammenzustellen, die etwas in dieser Richtung für eine meiner Apps tut.

Eines der wichtigsten Probleme, die ich lösen musste, war das Aktualisieren der ursprünglichen Liste, die zum Erstellen der Gruppe verwendet wurde, dh ich wollte nicht, dass ein Benutzer mit etwas anderen Kriterien sucht, um die gesamte Liste zu aktualisieren nur die Unterschiede.

In seiner derzeitigen Form wird es wahrscheinlich nicht alle Ihre Sortierfragen im Moment beantworten, aber es könnte ein guter Ausgangspunkt für andere sein.