Hi, schonmal sorry für die späte Antwort.
Zum Speicherverbrauch von
ObservableVector und
ObservableCollection. Wie gesagt, letzteres ist nicht virtualisiert und daher muss jedes Item bereits im Speicher vorliegen. Für den
ObservableVector hingegen werden die Items "on-demand" geladen und entladen.
Zum Thema Gruppieren und Sortieren:
Sortieren geht über das
SortOrder Property der
QueryOptions. Gruppieren kannst du über den ersten ctor Parameter der
QueryOptions und es dann, bei Bedarf, über die SearchFilter (
ApplicationSearchFilter,
UserSearchFilter) verfeinern.
Bzgl. der MusicProperties.
Wenn du die
Bulk-Access API benutzt (
IObservableVector), werden die Properties direkt mitgeladen, d.h. der Converter ist überflüssig. Bei einer regulären Suche musst du die Properties selber laden. Für Letzteres empfiehlt es sich
Property-Prefetching zu benutzen. I.d.R. werden die MusicProperties dadurch fast "instant" geladen. Allerdings kommst du um den "async call" nicht herum. Hinzu kommt, dass trotz Prefetching die Properties manchmal erst geladen werden müssen - zumindest hab ich das selber schon beobachtet.
Wie auch immer, das ganze muss in einem Converter geschehen und das heißt, die musst auf den "async call" warten (await geht nicht, wegen der Interface Definition). Das ganze geschieht im UI Thread und blockt daher die UI - meiner Meinung nach sollte man solche Situationen vermeiden.
So, nun der dicke Batzen - Gruppieren in der UI

Da du die Daten virtualisieren möchtest, ist das etwas komplexer. Dem Screenshot nach, möchtest du nicht wie in der Kontakte-App gruppieren (leider, den das ist ziemlich simpel).
Um das so hinzukriegen (mit C#) kannst du folgendes machen:
- Eine reguläre Suche ausführen und schonmal gruppieren, z.B. nach System.AlbumArtist. Kein PropertyPrefetch benutzen, aber auf jeden Fall den Windows Search Indexer mit einbeziehen (die Libraries werden indeziert, User die das manuell ausschalten würde ich eiskalt ignorieren)
- Da du bereits gruppiert hast, kannst du das DisplayName Property (z.B. Album-Titel, wenn du nach System.AlbumArtist gruppiertst) verwenden, um nun die eigentlichen Gruppen zu erhalten (Anfangsbuchstaben). Wie du mit Sonderzeichen umgehst weiß ich nicht, evtl. ist String-Normalisierung nötig.
- Wenn du nun alle Anfangsbuchstaben hast, führst du für jeden wieder einen Query aus, aber diesmal über die Bulk-Access API. ApplicationSearchFilter eignet sich hierzu.
Bis hier hast du nur die Daten. Das ganze dauert Zeit. Ich komme auf ca. 600-800ms bei einer 4GB Musik-Bibliothek und einer regulären HDD. Am Besten alles in einer Hintergrund-Task laufen lassen.
Der "Tricky-Part" ist nun das ganze auch anzuzeigen. Im ersten Post hab ich es ja schon geschrieben, Die Funktionalität ist imo nicht da, um Virtualisierung mit Gruppen zu benutzen, von daher musst du dir eine Alternative einfallen lassen. Hier ist meine
Ein paar Worte vorweg. Diese Lösung ist nicht optimal. Es wurde zwar Die Gruppen voll virtualisiert, aber nicht die einzelnen Items. D.h. hast du eine Gruppe mit 1000 Items, werden alle 1000 Items geladen. Sollte das bei dir der Fall sein, rate ich dir dazu
nicht mit C# zu arbeiten. Schau dir den Quelltext der XBox Music App an. Zu der Performance der App verlier ich aber mal besser kein Wort

Der Grund, das alle Items geladen werden ist, dass du für das zweite Grid Scrolling deaktivieren musst, um keinen doppelten Scroll-Balken zu bekommen. Dadurch wird die Virtualisierung deaktiviert und der Inhalt komplett geladen.
Also, was du brauchst. Hier ein kurzer Anriss der UI
- GridView - VirtualizingStackPanel
- GridView - WrapGrid
Das erste GridView hält die Gruppen, welche du in dem zweiten GridView anzeigen kannst.
Dieser Ansatz bringt jetzt ein paar Probleme mit sich.
- Für das erste GridView musst du den ItemContainerStyle komplett überschreiben, da sonst eine Gruppe als einzelnes Item behandelt wird. Das ist nicht schwer, aber mühsam.
- Das zweite GridView wird alle Maus-Klicks und Scroll-Infos schlucken und nicht an das erste Grid weitergeben, d.h. du musst Scrolling selber implementieren (klingt schlimm, ist einfach). Dies tritt auf, auch wenn du Scrolling explizit deaktivierst (was du tun musst). Microsoft hat schon angekündigt dies demnächst "zu fixen". Touch-Input ist davon nicht betroffen.
Zu 1.:
Wichtig ist, dass du
SelectionMode.None verwendest und alle Touch- und Klick-Transistionen entfernst.
Noch ein Tipp: Verwende auf jeden Fall eine
MinWidth für die einzelnen Gruppen-Items. Andernfalls werden alle mit 0 initiiert und dadurch alle Gruppen einmal geladen. Du weißt wo groß deine Gruppen sind, du musst das Wissen nur benutzen
Zu 2.:
Hier einfach nur der Code. Es gibt noch eine zweite Möglichkeit in der man einen extra EventHandler vom Zweiten in das ersten Grid registriert. Hatte damit aber immer wieder sehr merkwürdiges Scrollverhalten beobachtet.
Der Code muss in die Code-Behind Datei der entsprechenden Seite:
Code:
private ScrollViewer _scrollViewer;
private void OnWheelChanged( CoreWindow sender, PointerEventArgs args )
{
if ( _scrollViewer == null )
{
_scrollViewer = contentGrid.FindChild<ScrollViewer>( "ScrollViewer" );
}
if ( _scrollViewer == null )
{
return;
}
double offset = _scrollViewer.HorizontalOffset < 2 ? 2 : 0.2;
if ( args.CurrentPoint.Properties.MouseWheelDelta == -120)
{
_scrollViewer.ScrollToHorizontalOffset( _scrollViewer.HorizontalOffset + offset );
}
if ( args.CurrentPoint.Properties.MouseWheelDelta == 120 )
{
_scrollViewer.ScrollToHorizontalOffset( _scrollViewer.HorizontalOffset - offset );
}
args.Handled = true;
}
protected override void OnNavigatedTo( Windows.UI.Xaml.Navigation.NavigationEventArgs e )
{
base.OnNavigatedTo( e );
Window.Current.CoreWindow.PointerWheelChanged += OnWheelChanged;
}
protected override void OnNavigatedFrom( Windows.UI.Xaml.Navigation.NavigationEventArgs e )
{
Window.Current.CoreWindow.PointerWheelChanged -= OnWheelChanged;
base.OnNavigatedFrom( e );
}
Zum scrollen verwende ich einen festen Wert von 0.2. Du kannst den Offset auch berechnen, aber das Ergebnis taumelt irgendwo zwischen 0.18 und 0.22.
Versuch dich am Besten erstmal daran, die Gruppen bei Bedarf in das 1. Grid zu laden. Abhängig davon wie groß deine Gruppen sind, sollte nie mehr als 2-3 Gruppen gleichzeitig geladen sein.
Weiterhin ist es ratsam die Daten über die Musik-Bibliothek zu cachen. Den entsprechenden Query kann man als String speichern und wieder laden. Ändert sich die Collection wird ein Event gefeuert. So musst du auf jeden Fall nicht immer wieder alle Daten laden.
Da es sein kann, dass du länger als eine Sekunde auf die Daten warten musst, ist eine ProgressRing keine schlechte Idee.