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