gpt4 book ai didi

c# - UWP ObservableCollection 排序和分组

转载 作者:可可西里 更新时间:2023-11-01 08:27:23 24 4
gpt4 key购买 nike

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

在我见过的最简单的 UWP 示例中,通常有一个 ViewModel 公开一个 ObservableCollection,然后绑定(bind)到 View 中的 ListView。在 ObservableCollection 中添加或删除项目时,ListView 通过对 INotifyCollectionChanged 通知使用react来自动反射(reflect)更改。对于未排序或未分组的 ObservableCollection,这一切都很好,但如果集合需要排序或分组,似乎没有明显的方法来保留更新通知。此外,即时更改排序或分组顺序似乎会引发重大的实现问题。

++

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

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

这个 ObservableCollection 随时间变化,我们想在更新的 View 中呈现一个实时分组和排序的列表响应数据缓存的变化。我们还希望为用户提供在 LastName 和 State 之间即时切换分组的选项。

++

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

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

Cache _cache;

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

然后我们可以将其绑定(bind)到一个 View ,在该 View 中我们将 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 initial 之间交换。

++

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

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

和这篇文章

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

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

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

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

同时动态交换分组似乎需要为每个分组场景使用完全独立的分组项目的 ObservableCollection,并且要在运行时交换 ListView 的 ItemSource 绑定(bind)。

我在 UWP 环境中看到的其他内容似乎非常有用,所以我很惊讶地发现像分组和排序列表这样重要的东西会抛出障碍...

有人知道如何正确执行此操作吗?

最佳答案

我已经开始组装一个名为 GroupedObservableCollection 的库这对我的一个应用程序做了一些类似的事情。

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

在目前的形式下,它现在可能不会回答您所有的排序问题,但对于其他人来说它可能是一个很好的起点。

关于c# - UWP ObservableCollection 排序和分组,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34915276/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com