A part of the .NET Framework that provides a unified programming model for building line-of-business desktop applications on Windows.
Hello @Sergey Dorodnov ,
Thank you for providing the additional source code.
I recreated a test project using the relevant DataGrid configuration from the code you shared, including:
- A
CollectionViewSourcewith two group descriptions. - Sorting by
OrdinalandWorkStatus. -
IsAsync=Truein the supplied bindings. - Row and column virtualization.
-
IsVirtualizingWhenGrouping=True. -
VirtualizationMode="Recycling". -
ScrollUnit="Pixel". - A
DataGridCheckBoxColumn. - Multiple
DataGridTemplateColumnelements. - More than 100 rows.
I tested vertical and horizontal scrolling with grouping enabled, including scrolling to positions where the first row was only partially visible. However, I was unable to reproduce the horizontal displacement shown in your screenshot, where the cells and borders of the first visible row appear shifted slightly to the right.
My test reproduced the virtualization, grouping, sorting, and column types from the supplied code, but it did not include every style, template, converter, event handler, and bound component from the complete application. Therefore, the result indicates that the basic combination of grouping and Recycling virtualization is not sufficient by itself to reproduce the problem.
I also tested:
VirtualizingPanel.VirtualizationMode="Standard"
I agree that Standard mode can make scrolling noticeably slower.
Difference between Standard and Recycling
With Standard virtualization, WPF creates a container when an item enters the viewport and discards that container when the item leaves.
With Recycling virtualization, WPF reuses existing DataGridRow and DataGridCell containers for different data items. This generally improves scrolling performance because fewer controls, templates, and bindings need to be recreated.
This does not prove that Recycling itself is defective. However, it means that a style, trigger, event handler, or template that changes container state must also restore that state correctly when the container is reused.
Microsoft documentation:
VirtualizationMode Enum Optimize control performance in WPF
Remove the mouse-over editing trigger
The first test I recommend is temporarily removing this trigger:
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="IsEditing" Value="True" />
</Trigger>
Then test again with the original virtualization settings:
<DataGrid
VirtualizingPanel.IsVirtualizing="True"
VirtualizingPanel.IsVirtualizingWhenGrouping="True"
VirtualizingPanel.VirtualizationMode="Recycling"
VirtualizingPanel.ScrollUnit="Pixel"
ScrollViewer.CanContentScroll="True" />
Setting IsEditing from an IsMouseOver trigger causes the cell to switch between its display and editing templates during mouse movement. With Recycling enabled, the same cell container can later represent another item.
This is currently a hypothesis because I could not reproduce the displacement in the test project. However, the trigger directly affects cell state and template layout, so it should be isolated first.
Disable only column virtualization
Keep row recycling enabled, but temporarily disable column virtualization:
<DataGrid
EnableRowVirtualization="True"
EnableColumnVirtualization="False"
VirtualizingPanel.IsVirtualizing="True"
VirtualizingPanel.IsVirtualizingWhenGrouping="True"
VirtualizingPanel.VirtualizationMode="Recycling"
VirtualizingPanel.ScrollUnit="Pixel"
ScrollViewer.CanContentScroll="True" />
Use Item scrolling with Recycling
Before using Standard mode permanently, keep Recycling enabled and change only the scrolling unit:
<DataGrid
VirtualizingPanel.IsVirtualizing="True"
VirtualizingPanel.IsVirtualizingWhenGrouping="True"
VirtualizingPanel.VirtualizationMode="Recycling"
VirtualizingPanel.ScrollUnit="Item"
ScrollViewer.CanContentScroll="True" />
Pixel scrolling allows the viewport to stop between row boundaries, so the first visible row can be only partially displayed. Item scrolling moves the viewport by complete items.
Microsoft documentation:
VirtualizingPanel.ScrollUnit Property
Test template columns individually
Temporarily replace each DataGridTemplateColumn, one at a time, with a simple DataGridTextColumn:
<DataGridTextColumn
Header="Test"
Binding="{Binding SomeProperty}" />
After each replacement, reproduce the scrolling issue. If the issue disappears, restore that column and simplify its template by removing triggers, converters, nested panels, margins, and custom controls. Add them back one at a time to identify the element that does not behave correctly with recycled cells.
Test the CheckBox column separately
Temporarily replace the DataGridCheckBoxColumn with a text column bound to the same property:
<DataGridTextColumn
Header="Selected"
Binding="{Binding IsSelected}" />
If the issue disappears, inspect any styles applied to CheckBox, DataGridCell, or the checkbox column, especially settings such as:
Margin
Padding
HorizontalAlignment
HorizontalContentAlignment
RenderTransform
LayoutTransform
The built-in DataGridCheckBoxColumn alone did not reproduce the problem in my test, so this test is intended to determine whether an application-level checkbox or cell style is involved.
Remove custom cell and row styles
Temporarily test the DataGrid without custom row and cell styles:
<DataGrid
RowStyle="{x:Null}"
CellStyle="{x:Null}"
ColumnHeaderStyle="{x:Null}"
RowHeaderStyle="{x:Null}" />
If these properties inherit implicit styles from application resources, temporarily place simple local styles on the DataGrid:
<DataGrid.Resources>
<Style TargetType="{x:Type DataGridRow}">
<Setter Property="Margin" Value="0" />
<Setter Property="RenderTransform" Value="{x:Null}" />
<Setter Property="LayoutTransform" Value="{x:Null}" />
</Style>
<Style TargetType="{x:Type DataGridCell}">
<Setter Property="Margin" Value="0" />
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="RenderTransform" Value="{x:Null}" />
<Setter Property="LayoutTransform" Value="{x:Null}" />
</Style>
</DataGrid.Resources>
These styles are intended only as a diagnostic test. If they remove the displacement, compare them with the application’s original styles to find which setter or trigger changes the horizontal layout.
Temporarily remove event handlers
Temporarily remove DataGrid, row, and cell event handlers, particularly handlers for:
LoadingRow, UnloadingRow, BeginningEdit, PreparingCellForEdit, CellEditEnding, CurrentCellChanged, SelectionChanged, LayoutUpdated, ScrollChanged, MouseEnter, MouseLeave, MouseMove
Also check whether any handler changes:
Margin, Padding, Width, MinWidth, HorizontalAlignment, HorizontalContentAlignment, RenderTransform, LayoutTransform, IsEditing, Visibility
Simplify grouping templates
If the problem still occurs, temporarily remove custom group styles and group header templates while keeping grouping enabled.
Start with a simple group style:
<DataGrid.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<TextBlock
Margin="4"
Text="{Binding Name}" />
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
</DataGrid.GroupStyle>
If the displacement disappears, restore the original group template incrementally and check nested panels, expanders, margins, transforms, and width bindings.
Because IsVirtualizingWhenGrouping=True is enabled, group containers and item containers both participate in the scrolling layout. A custom grouping template should therefore be tested independently from the column templates.
Improve Standard-mode scrolling
If Standard mode is the only reliable workaround, deferred scrolling can reduce the visible delay:
<DataGrid
VirtualizingPanel.IsVirtualizing="True"
VirtualizingPanel.IsVirtualizingWhenGrouping="True"
VirtualizingPanel.VirtualizationMode="Standard"
VirtualizingPanel.ScrollUnit="Item"
ScrollViewer.CanContentScroll="True"
ScrollViewer.IsDeferredScrollingEnabled="True" />
When deferred scrolling is enabled, the DataGrid content remains stationary while the scrollbar thumb is being dragged. The content is updated after the user releases the thumb.
This does not provide a live preview while dragging, but it avoids repeatedly creating and arranging rows at every intermediate scrollbar position.
Microsoft documentation:
ScrollViewer.IsDeferredScrollingEnabled Property
DataGrid.EnableRowVirtualization Property
DataGrid.EnableColumnVirtualization Property
Based on the reported symptom, I suggest trying the workarounds in this order:
- Remove the
IsMouseOver:IsEditingtrigger. - Keep Recycling but use
ScrollUnit="Item". - Keep row recycling but disable column virtualization.
- Identify and simplify the specific template or style causing the problem.
- Use Standard virtualization with deferred scrolling only if the earlier options do not resolve it.
However, based on the XAML you shared, I also noticed that the DataGrid contains a large number of DataGridTemplateColumn, CheckBox controls, custom header templates, event handlers, and grouping templates. Since these elements interact closely with WPF's container recycling mechanism, the root cause may be related to a specific template or layout behavior rather than DataGrid virtualization itself. Further isolation testing by temporarily simplifying some templates may help identify which component is triggering the misalignment.
If this explanation and the diagnostic steps were helpful, I would appreciate it if you could follow the instructions here, so that others experiencing similar behavior can benefit from the answer as well.