I have a project that uses a DataGrid
with a custom template so that I can add a special row to the bottom of the data rows. I would like this special row to be pinned under the last row but not as part of the ScrollViewer
, such that it remains pinned under the last row until the bottom of the special row hits the bottom of the data grid, then I would like the rows region to size to the space inbetween and scroll accordingly, with the special row always visible.
So far, I have my special row as part of the ScrollViewer
along with the RowsPresenter
. Both the presenter and the special row are in auto-sized rows of a Grid
within the ScrollViewer
, with the ScrollViewer
in a star-sized grid row so that the scrollbar will appear when it runs out of space. How do I get from this, where the rows and special row scroll together to where I want to be, where the rows scroll, but the special row is pinned at the bottom and always visible?
Although my example uses a DataGrid
, I am sure this can be simplified down to just a scrollable element of varying height, and a control pinned beneath it. So far, I imagine I need a Canvas
rather than a Grid
to host my ScrollViewer
and companion special row, with some logic to adjust heights and positions when the ScrollViewer
grows (if I can detect that), but I haven't yet tried this. Is there a better way or is the Canvas
approach the best one available?
I was able to solve this by using a Grid
with two auto-sized rows; a row for the DataGrid
and a row for my pinned row. I then monitor the sizing of the Grid
and, upon resizing, look to see if the Grid
's ActualHeight is greater than the screen real-estate it is given to occupy. If it is, I change the DataGrid
's row to star-sized, which results in the pinned row appearing pinned to the bottom of the parent control and the DataGrid
displaying a scrollbar for its rows. I change the row back to auto-sizing when more space is made available.
This would obviously work for any scenario where one row must always be on screen but must also be pinned to the bottom of another.
The pinning code looks something like this:
RowDefinition row = this.mainGrid.RowDefinitions[0];
if (row.Height.GridUnitType == GridUnitType.Auto)
{
if (this.mainGrid.ActualHeight > this.ActualHeight)
{
row.Height = new GridLength(1, GridUnitType.Star);
}
}
else
{
if (this.dataGrid.DesiredSize.Height < row.ActualHeight)
{
row.Height = GridLength.Auto;
}
}
First, create a Grid for the main control and the pinned control:
<Grid Grid.Row="0" VerticalAlignment="Top">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<!-- The main control, that is about to stretch. -->
<sdk:DataGrid Grid.Row="0" ItemsSource="{Binding YOUR_COLLECTION}" />
<!-- The pinned control. -->
<TextBlock Grid.Row="1" Text="Hello World" />
</Grid>
The trick is VerticalAlignment="Top" - when the main control is smaller than the available height, it will move to the top of the available space and the pinned control will appear under it.
Then, put this Grid into a container that stretches vertically, for example in a row of another Grid with Star height:
<Grid x:Name="LayoutRoot">
<Grid.RowDefinitions>
<!-- RowDefition for the Grid with the main control and the pinned control. -->
<!-- If you want to have some other controls, -->
<!-- add other RowDefinitions and put these controls there. -->
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<!-- The internal Grid for the main control and the pinned control. -->
<Grid Grid.Row="0" VerticalAlignment="Top">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<sdk:DataGrid Grid.Row="0" ItemsSource="{Binding YOUR_COLLECTION}" />
<TextBlock Grid.Row="1" Text="Hello World" />
</Grid>
</Grid>
Instead of the root Grid you may have any other container that stretches vertically, the important thing is that it tries to fill all the available space for it.