2016-01-21 18 views
11

Trong các ứng dụng UWP, làm thế nào bạn có thể nhóm và sắp xếp một ObservableCollection và giữ tất cả sự tốt lành thông báo trực tiếp?Sắp xếp và nhóm UWP ObservableCollection

Trong hầu hết các ví dụ UWP đơn giản mà tôi đã thấy, thường có một ViewModel cho thấy một ObservableCollection mà sau đó được liên kết với một ListView trong khung nhìn. Khi các mục được thêm hoặc xóa khỏi ObservableCollection, ListView sẽ tự động phản ánh các thay đổi bằng cách phản ứng với các thông báo INotifyCollectionChanged. Tất cả điều này hoạt động tốt trong trường hợp của ObservableCollection chưa phân loại hoặc chưa được nhóm, nhưng nếu bộ sưu tập cần được sắp xếp hoặc nhóm, có vẻ như không có cách nào dễ dàng để bảo vệ các thông báo cập nhật. Hơn nữa, việc thay đổi thứ tự sắp xếp hoặc nhóm trên chuyến bay dường như làm nảy sinh các vấn đề triển khai quan trọng.

++

Đi một kịch bản mà bạn có một hiện datacache backend đó cho thấy nhiều một ObservableCollection của rất đơn giản lớp Contact.

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

ObservableCollection này thay đổi theo thời gian, và chúng tôi muốn trình bày một thời gian thực phân nhóm và sắp xếp danh sách trong quan điểm cho rằng cập nhật để đáp ứng với những thay đổi trong datacache. Chúng tôi cũng muốn cung cấp cho người dùng tùy chọn chuyển đổi nhóm giữa LastName và State khi đang di chuyển.

++

Trong thế giới WPF, điều này là tương đối nhỏ. Chúng ta có thể tạo một ViewModel đơn giản tham chiếu đến datacache trình bày bộ sưu tập Danh bạ của bộ nhớ cache như hiện tại.

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

    Cache _cache; 

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

Sau đó, chúng tôi có thể liên kết điều này với chế độ xem nơi chúng tôi triển khai tập hợp các định nghĩa CollectionViewSource và sắp xếp và nhóm dưới dạng tài nguyên 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> 

Sau đó, khi người dùng nhấp vào nút GroupBy ở cuối cửa sổ, chúng tôi có thể nhóm và sắp xếp bay trong mã sau.

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

Tất cả đều hoạt động tốt và các mục được cập nhật tự động khi thay đổi bộ nhớ cache dữ liệu thay đổi. Việc nhóm và chọn lựa Listview vẫn không bị ảnh hưởng bởi các thay đổi bộ sưu tập và các mục liên hệ mới được nhóm chính xác. Việc nhóm có thể được hoán đổi giữa Nhà nước và Tên người dùng ban đầu bởi người dùng lúc chạy.

++

Trong thế giới UWP, các CollectionViewSource không còn có GroupDescriptions và SortDescriptions bộ sưu tập, và sắp xếp/nhóm cần phải được thực hiện ở cấp ViewModel.Cách tiếp cận gần nhất với một giải pháp khả thi tôi đã tìm thấy là dọc theo dòng của gói mẫu của Microsoft tại

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

và bài viết này

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

nơi nhóm ViewModel ObservableCollection sử dụng LINQ và hiển thị nó cho chế độ xem dưới dạng ObservableCollection của các mục được nhóm

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

nơi GroupInfoList được định nghĩa là

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

này ít nhất đưa chúng ta một bộ sưu tập nhóm hiển thị trong View, nhưng bản cập nhật cho bộ sưu tập datacache không còn phản ánh trong thời gian thực. Chúng ta có thể nắm bắt sự kiện CollectionChanged của datacache và sử dụng nó trong viewmodel để làm mới bộ sưu tập GroupedContacts, nhưng điều này tạo ra một bộ sưu tập mới cho mọi thay đổi trong datacache, khiến ListView trở nên flicker và reset etc.

Ngoài ra, việc hoán đổi nhóm khi di chuyển có vẻ như yêu cầu một ObservableCollection riêng biệt của các mục được nhóm cho từng kịch bản nhóm và cho ràng buộc ItemSource của ListView được hoán đổi khi chạy.

Phần còn lại của những gì tôi đã nhìn thấy trong những môi trường UWP dường như cực kỳ hữu ích, vì vậy tôi ngạc nhiên khi thấy một cái gì đó như quan trọng như nhóm và phân loại danh sách ném lên các chướng ngại vật ...

Bất cứ ai cũng biết làm thế nào để làm điều này đúng không?

+1

Tại sao không nghe sự kiện từ bộ sưu tập cơ bản và cập nhật bộ sưu tập được nhóm khi thích hợp? Nó có thể đòi hỏi một chút nỗ lực hơn là xây dựng lại để làm việc ra nơi chính xác để đặt nó, nhưng tôi sẽ tưởng tượng đó là những gì CollectionViewSource đã thực hiện –

+0

Cảm ơn. Vâng bạn đã đúng. Về cơ bản, tôi hỏi xem liệu chúng ta có phải làm tất cả những gì chúng ta hay không hoặc nếu có một lựa chọn tốt hơn để tận dụng sức mạnh của lớp ObservableCollection hiện tại. Đó là một yêu cầu cơ bản như vậy mà tôi cảm thấy viết mã ống nước mỗi lần phải là một trường hợp phát minh lại bánh xe, và nền tảng đó phải có cách tiếp cận thích hợp được xây dựng. Có lẽ không ... – Nick

Trả lời

1

nhất nỗ lực cho đến nay sử dụng sau lớp helper 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); 
       } 
      } 
     } 

    } 
} 

nơi lớp Nhóm generic (mà bản thân cho thấy một ObservableCollection) xuất phát từ bài viết này

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

Để làm bản demo làm việc: -

Từ ứng dụng trống UWP mới, thêm trên ObservableGroupingCollection lớp học. Sau đó, thêm một tập tin lớp trong cùng một không gian tên và thêm tất cả các lớp học sau

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

Cuối cùng, chỉnh sửa Mainpage như sau

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

phương pháp HandleCollectionChanged chỉ xử lý Add/Remove cho đến nay và sẽ phá vỡ nếu NotifyCollectionChangedEventArgs tham số chứa nhiều mục (lớp ObservableCollection hiện tại chỉ thông báo cho từng thay đổi một)

Vì vậy, nó hoạt động tốt nhưng tất cả đều cảm thấy bị hack.

Đề xuất cải thiện rất được hoan nghênh.

5

Tôi đã bắt đầu tập hợp một thư viện có tên GroupedObservableCollection thực hiện điều gì đó dọc theo các dòng này cho một trong các ứng dụng của tôi.

Một trong những vấn đề chính mà tôi cần giải quyết là làm mới danh sách ban đầu được sử dụng để tạo nhóm, nghĩa là tôi không muốn người dùng tìm kiếm các tiêu chí hơi khác nhau khiến toàn bộ danh sách được làm mới , chỉ là sự khác biệt.

Ở dạng hiện tại, nó có thể sẽ không trả lời tất cả các câu hỏi sắp xếp của bạn ngay bây giờ, nhưng đây có thể là điểm khởi đầu tốt cho người khác.

Các vấn đề liên quan