I have a ListView
with which I try te emulate the Windows Explorer "List" view. To show the items, I use the VirtualizingWrapPanel
by S. Baeumlisberger and it scrolls horizontally and virtualizes its items just fine.
However, at a certain point users are going to want to group items (by file type for example). This is where things get tricky. When grouping, the ListView
uses the <GroupStyle.Panel>
to display several VirtualizingWrapPanel
's, one for each group of items.
For this <GroupStyle.Panel>
, I use a VirtualizingStackPanel
with it's Orientation
set to Horizontal
, because I want the groups to display horizontally, just like in Windows Explorer. This works, but it won't virtualize it's items (whereas it does when I set it to Vertical
).
This is my XAML:
<ListView x:Name="PART_ListView" SelectionMode="Extended"
FocusVisualStyle="{x:Null}" ScrollViewer.IsDeferredScrollingEnabled="False"
ScrollViewer.CanContentScroll="True" BorderThickness="0"
Grid.IsSharedSizeScope="True" VirtualizingPanel.ScrollUnit="Pixel"
VirtualizingPanel.IsVirtualizing="True" VirtualizingPanel.IsVirtualizingWhenGrouping="True"
VirtualizingPanel.VirtualizationMode="Recycling" VirtualizingPanel.CacheLength="2"
VirtualizingPanel.CacheLengthUnit="Page">
<ListView.Resources>
<GroupStyle x:Key="groupStyle">
<GroupStyle.Panel>
<ItemsPanelTemplate>
<VirtualizingStackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</GroupStyle.Panel>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Expander Margin="0,0,20,0" IsExpanded="True" Style="{DynamicResource lailaShell_GroupByExpanderStyle}">
<Expander.Resources>
<converters:ListViewGroupingHeightConverter x:Key="listViewGroupingHeightConverter" />
</Expander.Resources>
<Expander.Header>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Stretch" x:Name="expanderHeader">
<TextBlock Text="{Binding Name}" Foreground="DarkBlue" VerticalAlignment="Center" />
</StackPanel>
</Expander.Header>
<ItemsPresenter Margin="5,0,0,0">
<ItemsPresenter.Height>
<MultiBinding Converter="{StaticResource listViewGroupingHeightConverter}">
<Binding Path="." ElementName="expanderHeader" />
<Binding Path="ActualHeight" ElementName="PART_ListView" />
</MultiBinding>
</ItemsPresenter.Height>
</ItemsPresenter>
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</ListView.Resources>
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<vwp:VirtualizingWrapPanel SpacingMode="None">
<vwp:VirtualizingWrapPanel.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Items.GroupDescriptions.Count, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListView}}}" Value="0">
<Setter Property="vwp:VirtualizingWrapPanel.Margin" Value="20,0,0,0" />
</DataTrigger>
<DataTrigger Binding="{Binding Path=Items.GroupDescriptions.Count, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListView}}}" Value="1">
<Setter Property="vwp:VirtualizingWrapPanel.Margin" Value="0,0,0,0" />
</DataTrigger>
</Style.Triggers>
</Style>
</vwp:VirtualizingWrapPanel.Style>
</vwp:VirtualizingWrapPanel>
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<i:Interaction.Behaviors>
<behaviors:SelectionBehavior />
</i:Interaction.Behaviors>
<ListView.ItemTemplate>
<DataTemplate>
<Border>
<Grid>
<Grid.ColumnDefinitions >
<ColumnDefinition Width="16" />
<ColumnDefinition Width="4" />
<ColumnDefinition Width="16" />
<ColumnDefinition Width="4" />
<ColumnDefinition Width="Auto" SharedSizeGroup="A" />
</Grid.ColumnDefinitions>
<Image Source="{Binding PropertiesByKeyAsText[e77e90df-6271-4f5b-834f-2dd1f245dda4:2].FirstIcon16Async, IsAsync=True}"
Width="14" Height="16">
<Image.Style>
<Style>
<Setter Property="Image.Visibility" Value="Collapsed" />
<Style.Triggers>
<DataTrigger Binding="{Binding PropertiesByKeyAsText[e77e90df-6271-4f5b-834f-2dd1f245dda4:2].HasIcon}" Value="True">
<Setter Property="Image.Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
<Image Grid.Column="2" Source="{Binding IconAsync[16], IsAsync=True}" Width="16" Height="16"
UseLayoutRounding="True" SnapsToDevicePixels="True">
<Image.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding IsHidden}" Value="True">
<Setter Property="Image.Opacity" Value="0.5" />
</DataTrigger>
<DataTrigger Binding="{Binding IsCut}" Value="True">
<Setter Property="Image.Opacity" Value="0.5" />
</DataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
<Image Grid.Column="2" Source="{Binding OverlayImageAsync, IsAsync=True}" Width="16" Height="16"
UseLayoutRounding="True" SnapsToDevicePixels="True"
VerticalAlignment="Bottom" HorizontalAlignment="Left">
<Image.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding IsHidden}" Value="True">
<Setter Property="Image.Opacity" Value="0.5" />
</DataTrigger>
<DataTrigger Binding="{Binding IsCut}" Value="True">
<Setter Property="Image.Opacity" Value="0.5" />
</DataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
<TextBlock Grid.Column="4" MaxWidth="200"
Text="{Binding DisplayName}" TextTrimming="CharacterEllipsis">
<TextBlock.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding IsCompressed}" Value="True">
<Setter Property="TextBlock.Foreground" Value="Blue" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</Grid>
</Border>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
You can find the full source code at https://sourceforge.net/p/laila-shell/code/ci/master/tree/ if you're interested.
I don't known why, but I started getting in trouble with the VirtualizingWrapPanel
constantly flickering up to the point it became unusable and when I breaked the program I saw in the stack trace that it was constantly measuring and arranging. So I reckoned it had trouble sticking to a constant item size so I set the ItemSize
property of the VirtualizingWrapPanel
to something constant. And now all of a sudden all of my problems are gone and everything is working:
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<vwp:VirtualizingWrapPanel
SpacingMode="None"
Orientation="Vertical"
ItemSize="240,20">
</vwp:VirtualizingWrapPanel>
</ItemsPanelTemplate>
</ListView.ItemsPanel>