2016-01-21 70 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的庫,它爲我的某個應用程序沿着這些線做了一些事情。

我需要解決的關鍵問題之一是刷新用於創建組的原始列表,即我不希望用戶使用略有不同的標準進行搜索,導致整個列表被刷新,只是差異。

在目前的形式下,它可能不會立即回答你所有的排序問題,但它可能是其他人的一個很好的起點。