UWP ObservableCollection 排序和分组 [英] UWP ObservableCollection sorting and grouping

查看:20
本文介绍了UWP ObservableCollection 排序和分组的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在 UWP 应用中,如何对 ObservableCollection 进行分组和排序并保留所有实时通知的优点?

In UWP apps, how can you group and sort an ObservableCollection and keep all the live notification goodness?

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

In most simple UWP examples I've seen, there is generally a ViewModel that exposes an ObservableCollection which is then bound to a ListView in the View. When items are added or removed from the ObservableCollection, the ListView automatically reflects the changes by reacting to the INotifyCollectionChanged notifications. This all works fine in the case of an unsorted or ungrouped ObservableCollection, but if the collection needs to be sorted or grouped, there seems to be no readily apparent way to preserve the update notifications. What's more, changing the sort or group order on the fly seems to throw up significant implementation issues.

++

假设您有一个现有的数据缓存后端,它公开了一个非常简单的类 Contact 的 ObservableCollection.

Take a scenario where you have an existing datacache backend that exposes an ObservableCollection of very simple class Contact.

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

这个 ObservableCollection 随着时间的推移而变化,我们希望在更新的视图中呈现一个实时分组和排序的列表以响应数据缓存中的更改.我们还希望让用户可以选择在 LastName 和 State 之间动态切换分组.

This ObservableCollection changes over time, and we want to present a realtime grouped and sorted list in the view that updates in response to changes in the datacache. We also want to give the user the option to switch the grouping between LastName and State on the fly.

++

在 WPF 世界中,这是相对微不足道的.我们可以创建一个简单的 ViewModel 引用数据缓存,该数据缓存按原样呈现缓存的联系人集合.

In a WPF world, this is relatively trivial. We can create a simple ViewModel referencing the datacache that presents the cache's Contacts collection as-is.

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

    Cache _cache;

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

然后我们可以将其绑定到一个视图,在该视图中我们将 CollectionViewSource 和 Sort 和 Group 定义实现为 XAML 资源.

Then we can bind this to a view where we implement a CollectionViewSource and Sort and Group definitions as XAML resources.

<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 按钮时,我们可以在代码隐藏中动态分组和排序.

Then when the user clicks on the GroupBy buttons at the bottom of the window we can we can group and sort 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);
     }
}

这一切正常,当数据缓存集合发生变化时,项目会自动更新.Listview 的分组和选择不受集合更改的影响,并且新的联系人项目已正确分组.用户可以在运行时在 State 和 LastName 初始值之间交换分组.

This all works fine, and the items are updated automatically as the data cache collection changes. The Listview grouping and selection remains unaffected by collection changes, and the new contact items are correctly grouped.The grouping can be swapped between State and LastName initial by user at runtime.

++

在 UWP 世界中,CollectionViewSource 不再有 GroupDescriptions 和 SortDescriptions 集合,排序/分组需要在 ViewModel 层面进行.我发现的最接近可行解决方案的方法是沿着微软的示例包

In the UWP world, the CollectionViewSource no longer has the GroupDescriptions and SortDescriptions collections, and sorting/grouping need to be carried out at the ViewModel level. The closest approach to a workable solution I've found is along the lines of Microsoft's sample package at

https://github.com/Microsoft/Windows-通用样本/树/主/样本/XamlListView

还有这篇文章

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

ViewModel 使用 Linq 对 ObservableCollection 进行分组,并将其作为分组项的 ObservableCollection 呈现给视图

where the ViewModel groups the ObservableCollection using Linq and presents it to the view as an ObservableCollection of grouped items

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 定义为

where GroupInfoList is defined as

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

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

This does at least get us a grouped collection displayed in the View, but updates to the datacache collection are no longer reflected in real time. We could capture the datacache's CollectionChanged event and use it in the viewmodel to refresh the GroupedContacts collection, but this creates a new collection for every change in the datacache, causing the ListView to flicker and reset selection etc which is clearly suboptimal.

同时动态交换分组似乎需要一个完全独立的 ObservableCollection,用于每个分组场景的分组项目,并在运行时交换 ListView 的 ItemSource 绑定.

Also swapping the grouping on the fly would seem to require a completely seperate ObservableCollection of grouped items for each grouping scenario, and for the ListView's ItemSource binding to be swapped at runtime.

我所看到的 UWP 环境的其余部分似乎非常有用,所以我很惊讶地发现一些与分组和排序列表一样重要的东西会抛出障碍......

The rest of what I've seen of the UWP environment seems extremely useful, so I'm surprised to find something as vital as grouping and sorting lists throwing up obstacles...

有人知道如何正确地做到这一点吗?

Anyone know how to do this properly?

推荐答案

我已经开始整理一个名为 GroupedObservableCollection 的库 为我的一个应用程序做了一些类似的事情.

I've started putting together a library called GroupedObservableCollection that does something along these lines for one of my apps.

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

One of the key problems that I needed to solve was the refreshing of the original list that was used to create the group, i.e. I didn't want a user searching with slightly different criteria to cause the whole list to be refreshed, just the differences.

以目前的形式,它现在可能无法回答您所有的排序问题,但对其他人来说可能是一个很好的起点.

In its current form it probably won't answer all your sorting questions right now, but it might be a good starting point for others.

这篇关于UWP ObservableCollection 排序和分组的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆