As far as I know, ListBox
still stops virtualisation of items when groups are applied.
Whether 2000 items would perform adequately will depend upon the complexity of the template applied to each item. I have a ListBox
with a relatively simple template (about 8 TextBlock
s in a horizontal StackPanel
) and performance starts to degrade at around 1500 items with grouping applied. It also seems to depend upon the number of groups into which the items are aggregated, where a greater number of groups results in less performance. This is especially noticeable when scrolling for some reason.
ListBox
makes dynamic grouping very easy, but if you're usually going to be grouping by album then it might be better to set the ItemsSource
of your ItemsControl
(maybe a ListBox
) to be a collection of Album
objects, each of which has a Tracks
property that is itself a collection of Track
objects. Assuming this, I see two options:
- Use nested
ItemsControls
in the Album
DataTemplate
- Use a
HeaderedItemsControl
such as TreeView
with a HierarchicalDataTemplate
In option one, you have to manage the selection manually. You could, in the simplest implementation, have the ability to select albums and tracks separately; potentially having a track selected that didn't belong to the selected album. You may be able to do without selection of an album as this isn't a concept present in the track-list view of other media players I can think of.
Solution one also has implications for keyboard navigation from the last track of one album to the first track of the next album.
Assuming the following code:
public class Album
{
public string Title { get; set; }
public ObservableCollection<Track> Tracks { get; set; }
}
public class Track
{
public string Title { get; set; }
}
_tracks.ItemsSource = new[] {
new Album {
Title = "Album 1",
Tracks = new ObservableCollection<Track> {
new Track { Title = "Track 1" },
new Track { Title = "Track 2" }
}
},
new Album {
Title = "Album 2",
Tracks = new ObservableCollection<Track> {
new Track { Title = "Track 1" },
new Track { Title = "Track 2" }
}
}
};
Here's some code that demonstrates option one:
<ListBox x:Name="_tracks">
<FrameworkElement.Resources>
<DataTemplate DataType="{x:Type local:Track}">
<TextBlock Text="{Binding Path=Title}" />
</DataTemplate>
<DataTemplate DataType="{x:Type local:Album}">
<StackPanel>
<TextBlock Text="{Binding Path=Title}" />
<ListBox ItemsSource="{Binding Path=Tracks}" />
</StackPanel>
</DataTemplate>
</FrameworkElement.Resources>
</ListBox>
Change the outer ListBox
to an ItemsControl
to alleviate the selection issue, as discussed. You'll have to make it look pretty though as the above looks pretty ugly.
Option two could be defined like this:
<TreeView x:Name="_tracks2">
<FrameworkElement.Resources>
<DataTemplate DataType="{x:Type local:Track}">
<TextBlock Text="{Binding Path=Title}" />
</DataTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:Album}"
ItemsSource="{Binding Path=Tracks}">
<TextBlock Text="{Binding Path=Title}" />
</HierarchicalDataTemplate>
</FrameworkElement.Resources>
</TreeView>
ListView
supports opt-in UI virtualisation as of 3.5SP1 via the XAML attribute:
VirtualizingStackPanel.IsVirtualizing="True"
Bea Stollnitz has three great posts on this topic, though as she points out they're out of date since SP1.