В приложениях UWP, как вы можете группировать и сортировать ObservableCollection и сохранять все уведомления в реальном времени?
В большинстве простых примеров UWP, которые я видел, обычно существует ViewModel, который предоставляет ObservableCollection, который затем привязан к ListView в представлении. Когда элементы добавляются или удаляются из ObservableCollection, ListView автоматически отражает изменения, реагируя на уведомления INotifyCollectionChanged. Все это прекрасно работает в случае несортированного или негруппового ObservableCollection, но если сбор необходимо отсортировать или сгруппировать, похоже, нет очевидного способа сохранить уведомления об обновлениях. Что еще, изменение порядка сортировки или группы на лету, похоже, бросает серьезные проблемы с реализацией.
++
Возьмите сценарий, в котором у вас есть существующий бэкэнд datacache, который предоставляет ObservableCollection очень простого класса Contact.
public class Contact
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string State { get; set; }
}
Этот ObservableCollection изменяется со временем, и мы хотим представить в режиме реального времени сгруппированный и отсортированный список в представлении, которое обновляет в ответ на изменения в данных. Мы также хотим дать пользователю возможность переключаться между LastName и State на лету.
++
В мире WPF это относительно тривиально. Мы можем создать простой ViewModel, ссылающийся на datacache, который представляет коллекцию контактов кэша as-is.
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 больше не имеет коллекций GroupDescription и 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 группирует ObservableCollection с помощью Linq и представляет его представлению в виде 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; }
}
Это, по крайней мере, дает нам сгруппированную коллекцию, отображаемую в представлении, но обновления коллекции datacache больше не отражаются в режиме реального времени. Мы могли бы захватить событие CollectionChanged для данных datacache и использовать его в модели view, чтобы обновить коллекцию GroupedContacts, но это создает новую коллекцию для каждого изменения в datacache, заставляя ListView мерцать и reset выбор и т.д., Который явно субоптимален.
Кроме того, для замены группировки "на лету" требуется совершенно отдельный ObservableCollection сгруппированных элементов для каждого сценария группировки, а привязка элемента ListView ItemSource должна быть заменена во время выполнения.
Остальная часть того, что я видел в среде UWP, кажется чрезвычайно полезной, поэтому я удивлен, обнаружив что-то столь же жизненно важное, как группировка и сортировка списков, бросающих препятствия...
Кто-нибудь знает, как это сделать правильно?