2016-01-21 95 views
11

在UWP应用程序中,如何对ObservableCollection进行分组和排序,并保持所有实时通知的良好性?UWP ObservableCollection排序和分组

在我见过的大多数简单的UWP示例中,通常都有一个ViewModel公开一个ObservableCollection,然后绑定到View中的ListView。 当从ObservableCollection中添加或删除项目时,ListView通过对INotifyCollectionChanged通知作出反应来自动反映更改。在未分类或未分组的ObservableCollection中,这一切都可以正常工作,但是如果需要对集合进行排序或分组,则似乎没有明显的方式来保留更新通知。更重要的是,动态改变排序或群组顺序似乎会引发重大的实施问题。

++

拿,你有一个现有的数据高速缓存后端暴露出非常简单的类联系的一个ObservableCollection的场景。

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

此的ObservableCollection变化随着时间的推移,我们希望呈现分组的实时和在响应的数据高速缓存的变化更新 视图排序列表。我们还想让用户可以选择在即时状态下切换姓氏和状态之间的分组。

++

在WPF世界中,这是相对平凡的。 我们可以创建一个简单的ViewModel,引用显示缓存的Contacts集合的数据缓存。

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

    Cache _cache; 

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

然后,我们可以将它绑定到一个视图,我们将CollectionViewSource和Sort和Group定义实现为XAML资源。

<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> 

然后,当用户点击按钮的GroupBy在窗口的底部,我们可以,我们可以分组和排序的代码隐藏飞。

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); 
    } 
} 

这一切工作正常,并且项目被作为数据缓存收集的变化自动更新。 Listview分组和选择不受收集更改的影响,并且新联系人项目已正确分组。可以在运行时由用户初始化State和LastName之间的分组。

++

在UWP世界中,CollectionViewSource不再具有GroupDescriptions和SortDescriptions集合,和排序/分组需要在视图模型级别进行。一个可行的解决方案,我发现的最接近的方法是沿微软的样本包的线在

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

和本文

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

在视图模型组使用LINQ的的ObservableCollection和将其作为ObservableCollection的分组项目呈现

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; 
} 

其中GroupInfoList被定义为

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

但这至少让我们在视图中显示的分组集合,但更新的数据高速缓存集合不再是实时反映。我们可以捕获数据缓存的CollectionChanged事件并在viewmodel中使用它来刷新GroupedContacts集合,但是这会为数据缓存中的每个更改创建一个新的集合,导致ListView闪烁并重置选择等等,这显然是不理想的。

同时交换分组似乎需要为每个分组方案完全分开的ObservableCollection分组项目,并且需要在运行时交换ListView的ItemSource绑定。

的是我所见过的UWP环境的其余部分似乎是非常有用的,所以我很惊讶地发现,作为分组的重要和排序列表投掷障碍的东西......

任何人都知道该怎么做这适当吗?

+1

为什么不听基础集合的事件,并根据需要更新分组集合?它可能需要比重建工作更多的努力来找出究竟在哪里放置它,但我会想象这就是CollectionViewSource会做的 –

+0

谢谢。你是对的。我基本上是问我们是否必须自己做所有事情,或者是否有更好的选择来利用现有ObservableCollection类的力量。这是一个基本要求,我觉得编写管道代码每次都必须是重新发明轮子的情况,并且平台必须内置适当的方法。也许不是...... – Nick

回答

1

尽力到目前为止使用下面的辅助类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); 
       } 
      } 
     } 

    } 
} 

,其中通用分组类(其本身暴露了一个ObservableCollection)来自本文

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

为了使工作演示: -

从新的UWP空白应用程序中,添加上面的ObservableGroupingCollection类。然后,在相同的命名空间添加另一个类文件,并添加以下所有类别

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

最后,编辑的MainPage如下

<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方法只能处理添加/删除到目前为止,将打破,如果NotifyCollectionChangedEventArgs参数包含多个项目(现有的ObservableCollection类一次只通知一个更改)

所以它工作正常,但它都感觉有点哈克。

改进建议非常受欢迎。

5

我已经开始组建一个名为GroupedObservableCollection的库,它为我的某个应用程序沿着这些线做了一些事情。

我需要解决的关键问题之一是刷新用于创建组的原始列表,即我不希望用户使用略有不同的标准进行搜索,导致整个列表被刷新,只是差异。

在目前的形式下,它可能不会立即回答你所有的排序问题,但它可能是其他人的一个很好的起点。