Wie können Sie in UWP-Apps eine ObservableCollection gruppieren und sortieren und die Live-Benachrichtigungsgüte beibehalten?UWP ObservableCollection Sortierung und Gruppierung
In den meisten einfachen UWP-Beispielen, die ich gesehen habe, gibt es im Allgemeinen ein ViewModel, das eine ObservableCollection verfügbar macht, die dann an eine ListView in der Ansicht gebunden ist. Wenn Elemente zur ObservableCollection hinzugefügt oder daraus entfernt werden, spiegelt das ListView die Änderungen automatisch wider, indem es auf die INotifyCollectionChanged-Benachrichtigungen reagiert. Dies funktioniert im Fall einer unsortierten oder nicht gruppierten ObservableCollection einwandfrei. Wenn die Auflistung jedoch sortiert oder gruppiert werden muss, scheint es keine offensichtliche Möglichkeit zu geben, die Updatebenachrichtigungen beizubehalten. Darüber hinaus scheint das Ändern der Sortier- oder Gruppenreihenfolge im laufenden Betrieb zu erheblichen Implementierungsproblemen zu führen.
++
ein Szenario, wo Sie ein vorhandenes Data Backend haben, die eine ObservableCollection von sehr einfachen Klasse Kontakt aussetzt.
public class Contact
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string State { get; set; }
}
Diese ObservableCollection Veränderungen im Laufe der Zeit, und wir wollen eine Echtzeit präsentieren gruppiert und sortiert die Liste in der Ansicht, die als Reaktion auf Änderungen in der DataCache- aktualisiert. Wir möchten dem Benutzer auch die Möglichkeit geben, die Gruppierung zwischen LastName und State on the fly zu wechseln.
++
In einer WPF-Welt ist dies relativ trivial. Wir können ein einfaches ViewModel erstellen, das auf den Datencache verweist, der die Contacts-Auflistung des Caches unverändert darstellt.
public class WpfViewModel
{
public WpfViewModel()
{
_cache = GetCache();
}
Cache _cache;
public ObservableCollection<Contact> Contacts
{
get { return _cache.Contacts; }
}
}
Dann können wir dies zu einer Ansicht binden, wo wir eine Collection implementieren und Sortier- und Gruppierungsdefinitionen als XAML-Ressourcen.
<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>
Dann, wenn der Benutzer klickt auf die GroupBy Schaltflächen am unteren Rand des Fensters können wir wir können Gruppe und Art 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);
}
}
Dies funktioniert alles gut, und die Elemente werden automatisch aktualisiert, wenn sich die Datencache-Sammlung ändert. Die Listenansichtsgruppierung und -auswahl bleibt von den Sammlungsänderungen unberührt, und die neuen Kontaktelemente sind korrekt gruppiert. Die Gruppierung kann zur Laufzeit zwischen Benutzerstatus und Nachname durch Benutzer ausgetauscht werden.
++
In der UWP Welt, die Collection hat nicht mehr die Groupdescriptions und SortDescriptions Sammlungen und Sortierung/Gruppierung muß auf der Ansichtsmodell Ebene durchgeführt werden.Die nächste Annäherung an eine praktikable Lösung ist entlang der Linien von Microsofts Beispielpaket
ich gefunden habehttps://github.com/Microsoft/Windows-universal-samples/tree/master/Samples/XamlListView
und diesen Artikel
http://motzcod.es/post/94643411707/enhancing-xamarinforms-listview-with-grouping
wo das Ansichtsmodell Gruppen die ObservableCollection mit Linq und präsentiert es der Ansicht als eine ObservableCollection von gruppierten Elementen
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;
}
wo GroupInfoList ist definiert als
public class GroupInfoList : List<object>
{
public object Key { get; set; }
}
Dies gilt zumindest bekommen wir eine gruppierte Sammlung in der Ansicht angezeigt, aber Aktualisierungen der DataCache- Sammlung sind nicht mehr in Echtzeit wider. Wir könnten das CollectionChanged-Ereignis des Datencaches erfassen und es im Viewmodel verwenden, um die GroupedContacts-Auflistung zu aktualisieren. Dadurch wird jedoch für jede Änderung im Datencache eine neue Auflistung erstellt, die ListView zum Flimmern und Zurücksetzen der Auswahl usw. veranlasst, was eindeutig suboptimal ist.
Auch das Austauschen der Gruppierung im laufenden Betrieb scheint eine vollständig separate ObservableCollection von gruppierten Elementen für jedes Gruppierungsszenario zu erfordern und die ItemSource-Bindung der ListView zur Laufzeit zu vertauschen.
Der Rest von dem, was ich habe extrem nützlich der UWP-Umgebung zu sehen scheint, also bin ich überrascht, um etwas so wichtig wie Gruppierung zu finden und Sortieren von Listen Hindernisse werfen ...
Wer weiß, wie zu tun das richtig?
Warum nicht auf Ereignisse aus der zugrunde liegenden Sammlung hören und die gruppierte Sammlung entsprechend aktualisieren? Es kann etwas mehr Aufwand als Wiederaufbau erfordern, um herauszufinden, wo genau es hingestellt wird, aber ich würde mir vorstellen, dass das die CollectionViewSource getan hätte –
Danke. ja, du hast Recht. Ich frage im Grunde, ob wir all das selbst tun müssen oder ob es eine bessere Alternative gibt, die die Macht der bestehenden ObservableCollection-Klasse nutzt. Es ist so eine Grundvoraussetzung, dass ich das Gefühl habe, den Klempnercode jedes Mal neu schreiben zu müssen, um das Rad neu zu erfinden, und dass die Plattform einen geeigneten Ansatz haben muss. Vielleicht nicht ... – Nick