I'm tasked with implementing a custom DataGrid-like control in one of my projects.
This is due to many specific features it needs to have implemented, and also the large amounts of data that have to be displayed. We're talking about a million rows on average, sometimes more.
So naturally I looked into virtualization and how to implement the built-in features WPF has, but I seem to have no chance getting it to work. I've also spent countless hours trying to find more resources on this, and all I can find is people using either the native ListBox or DataGrid - no custom implementations.
The problem is that my custom grid attempts to load all UI elements (rows) into view right away - crushing the memory and CPU load.
This is my source code. Perhaps someone proficient enough in WPF can help me pinpoint any possible cause of the VirtualizingStackPanel seemingly not working.
I'm unsure whether the code-behind here is needed, the control itself renders just fine. Of course, I'm also open to any alternatives, such as using DataGrid and replacing its templates instead, or writing the virtualization logic myself, if that seems more feasible due to any limitations.
<!-- Cell inherits ContentControl -->
<Style TargetType="{x:Type local:Cell}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:Cell}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<ContentPresenter HorizontalAlignment="Stretch"
VerticalAlignment="Center"
Margin="{TemplateBinding Padding}" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!-- Row inherits ContentControl -->
<Style TargetType="{x:Type local:Row}">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="Margin" Value="0,0,0,1"/>
<Setter Property="Height" Value="24"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:Row}">
<Border>
<ItemsControl ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Grid}}, Path=Columns}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Rows="1" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type local:ColumnDefinition}">
<local:Cell Background="White"
Padding="4,2"
Height="24"
Margin="1">
<TextBlock>
<TextBlock.Text>
<MultiBinding Converter="{StaticResource CellConverter}">
<Binding RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type local:Row}}"
Path="DataContext" />
<Binding Path="." />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</local:Cell>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!-- Grid inherits ItemsControl -->
<Style TargetType="{x:Type local:Grid}">
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<VirtualizingStackPanel VirtualizationMode="Recycling"
IsVirtualizing="True"
CacheLength="1,2"
CacheLengthUnit="Item"
CanVerticallyScroll="True"
ScrollUnit="Item"
Orientation="Vertical"
IsItemsHost="True" />
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:Grid}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<ItemsControl Grid.Row="0"
ItemsSource="{TemplateBinding Columns}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Rows="1" Height="40" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"
Background="#212224"
Padding="8,4"
FontWeight="Bold"
Foreground="#fafcfe"
MinWidth="128" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<ItemsPresenter Grid.Row="1"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>