How does WPF DataGrid get frozen rows / columns?

I created a custom control based on a Grid (not a DataGrid) that is wrapped in a ScrollViewer. Now I would like to have frozen rows / columns, as in a DataGrid, but could not figure out how to do this.

Can someone give me some idea how this is done in WPF DataGrid?

+7
source share
2 answers

After this problem, I want to share what I have discovered so far.

DataGrid uses two different methods for this.


First: RowHeader


This is a simplified Template for DataGridRow :

 <Border x:Name="DGR_Border" ... > <SelectiveScrollingGrid> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto"/> <ColumnDefinition Width="*"/> </Grid.ColumnDefinitions> <Grid.RowDefinitions> <RowDefinition Height="*"/> <RowDefinition Height="Auto"/> </Grid.RowDefinitions> <DataGridRowHeader Grid.RowSpan="2" SelectiveScrollingGrid.SelectiveScrollingOrientation="Vertical" ... /> <DataGridCellsPresenter Grid.Column="1" ... /> <DataGridDetailsPresenter Grid.Column="1" Grid.Row="1" SelectiveScrollingGrid.SelectiveScrollingOrientation="{Binding RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}, Path=AreRowDetailsFrozen, Converter={x:Static DataGrid.RowDetailsScrollingConverter}, ConverterParameter={x:Static SelectiveScrollingOrientation.Vertical}}" ... /> </SelectiveScrollingGrid> </Border> 

As you can see, the DataGrid uses the attached property SelectiveScrollingOrientation to hold the RowHeader in position. If this property is set (or changed), it creates an adapted TranslateTransform ScrollViewer with the parent ScrollViewer Offset for the element. See the source code for more details.


Second: FrozenColumns


This stuff takes place in a DataGridCellsPanel ArrangeOverride() . It uses the private ArrangeState class to maintain state between the locations of multiple children.

 private class ArrangeState { public ArrangeState() { FrozenColumnCount = 0; ChildHeight = 0.0; NextFrozenCellStart = 0.0; NextNonFrozenCellStart = 0.0; ViewportStartX = 0.0; DataGridHorizontalScrollStartX = 0.0; OldClippedChild = null; NewClippedChild = null; } public int FrozenColumnCount { get; set; } public double ChildHeight { get; set; } public double NextFrozenCellStart { get; set; } public double NextNonFrozenCellStart { get; set; } public double ViewportStartX { get; set; } public double DataGridHorizontalScrollStartX { get; set; } public UIElement OldClippedChild { get; set; } public UIElement NewClippedChild { get; set; } } 

After initializing the state with

 private void InitializeArrangeState(ArrangeState arrangeState) { DataGrid parentDataGrid = ParentDataGrid; double horizontalOffset = parentDataGrid.HorizontalScrollOffset; double cellsPanelOffset = parentDataGrid.CellsPanelHorizontalOffset; arrangeState.NextFrozenCellStart = horizontalOffset; arrangeState.NextNonFrozenCellStart -= cellsPanelOffset; arrangeState.ViewportStartX = horizontalOffset - cellsPanelOffset; arrangeState.FrozenColumnCount = parentDataGrid.FrozenColumnCount; } 

he calls

 ArrangeChild(children[childIndex] as UIElement, i, arrangeState); 

for all implemented children and calculates the estimated width for unrealized children / columns.

 double childSize = GetColumnEstimatedMeasureWidth(column, averageColumnWidth); arrangeState.NextNonFrozenCellStart += childSize; 

At the end, the values ​​will be set in the appropriate fields in the DataGrid .

 private void FinishArrange(ArrangeState arrangeState) { DataGrid parentDataGrid = ParentDataGrid; // Update the NonFrozenColumnsViewportHorizontalOffset property of datagrid if (parentDataGrid != null) { parentDataGrid.NonFrozenColumnsViewportHorizontalOffset = arrangeState.DataGridHorizontalScrollStartX; } // Remove the clip on previous clipped child if (arrangeState.OldClippedChild != null) { arrangeState.OldClippedChild.CoerceValue(ClipProperty); } // Add the clip on new child to be clipped for the sake of frozen columns. _clippedChildForFrozenBehaviour = arrangeState.NewClippedChild; if (_clippedChildForFrozenBehaviour != null) { _clippedChildForFrozenBehaviour.CoerceValue(ClipProperty); } } 

Details for ArrangeChild(UIElement child, int displayIndex, ArrangeState arrangeState) can be found on line 1470 in the source code .


Conclusion


It is not like just making the columns frozen. Even if this works (except for clipping and scrollbar across the entire width)

 <ListView ItemsSource="some rows"> <ListView.ItemTemplate> <DataTemplate> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition /> <ColumnDefinition /> </Grid.ColumnDefinitions> <TextBlock Grid.Column="0" Text="Fixed" Background="LightBlue" Width="300" SelectiveScrollingGrid.SelectiveScrollingOrientation="Vertical" /> <TextBlock Grid.Column="1" Text="Scrolled" Background="LightGreen" Width="300" /> </Grid> </DataTemplate> </ListView.ItemTemplate> </ListView> 

it will not be:

 <ScrollViewer HorizontalScrollBarVisibility="Auto"> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition /> <ColumnDefinition /> </Grid.ColumnDefinitions> <TextBlock Grid.Column="0" Text="Fixed" Background="LightBlue" Width="300" SelectiveScrollingGrid.SelectiveScrollingOrientation="Vertical" /> <TextBlock Grid.Column="1" Text="Scrolled" Background="LightGreen" Width="300" /> </Grid> </ScrollViewer> 

The reason is that DataGridHelper.FindVisualParent<ScrollViewer>(element) (see line 149 in the code ) in the SelectiveScrollingOrientation attached property fails, you may find workarounds, for example. create your own attached property with a copy of the source code, but get a ScrollViewer by name. Otherwise, I think you should do a lot of things from scratch.

+4
source

Datagrid Column and Row has the "Frozen" property

if you want to freeze a column, I recommend that you do the following

either you want it in the selected Row or Column event and then in Event Get Column / Row and mark it as Frozen = true

or create another button or context menu with the right mouse button on which you lock / unlock currently marked

column / row

hope this helps

0
source

All Articles