WPF DataGrid Customization: Resizing Row/Column through DataGridCell gridlines

In this post I am going to share my experience on how I achieved the functionality to resize DataGrid row/column from any cell in that row (not just the row header). We needed this functionality as our user can hide the DataGrid headers and it is not possible to resize the rows/columns after that.

Resizing capabilities in DataGrid are provided through various Thumb controls added in row & column headers, so it’s obvious that we would have to add our own thumbs(or GridSplitter) to achieve this. So we need to create a DataTemplate for DataGridCell having these thumbs -

<WpfToolkit:DataGrid.CellStyle>
  <Style TargetType="{x:Type WpfToolkit:DataGridCell}">
     <Style.Setters>
        <Setter Property="Template">
           <Setter.Value>
               <ControlTemplate TargetType="{x:Type WpfToolkit:DataGridCell}">
                   <Grid>
                       <Border
                           Background="{TemplateBinding Background}"
                           BorderBrush="{TemplateBinding BorderBrush}"
                           BorderThickness="{TemplateBinding BorderThickness}"
                           Padding="{TemplateBinding Padding}"
                           SnapsToDevicePixels="True">
                             <ContentPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
                       </Border>
 
                       <Thumb
                           Width="4"
                           HorizontalAlignment="Right"
                           DragDelta="CellRightThumbDragDelta"
                           Style="{StaticResource ColumnGripperStyle}"
                           Tag="{Binding BindsDirectlyToSource=True, 
                                   RelativeSource={RelativeSource AncestorType={x:Type WpfToolkit:DataGridCell}}}" />
 
                       <Thumb
                           Height="4"
                           VerticalAlignment="Bottom"
                           Cursor="SizeNS"
                           DragDelta="CellBottomThumbDragDelta"
                           Style="{StaticResource ColumnGripperStyle}"
                           Tag="{Binding BindsDirectlyToSource=True, 
                                   RelativeSource={RelativeSource AncestorType={x:Type WpfToolkit:DataGridRow}}}" />
                   </Grid>
               </ControlTemplate>
            </Setter.Value>
       </Setter>
       <Setter Property="Margin" Value="0" />
       <Setter Property="Padding" Value="0" />
       <Setter Property="BorderThickness" Value="1" />
    </Style.Setters>
  </Style>
</WpfToolkit:DataGrid.CellStyle>

We have added two thumbs one at the bottom for resizing row and another on the right side to resize the column and handled there DragDelta event. Some important things to note about above template are -

1. For column thumb, we are passing the current cell through the Tag property, which will be used in DragDelta event handler.

2. For row thumb we are passing the current Row instead of cell.

3. We have placed these controls(Border and thumbs) inside a Grid so that thumbs don’t waste the space inside the cell(using DockPanel etc. will reserve the space for thumbs, which will cause empty space to be shown at the bottom and right of the cell content)

Here are the event handlers for above thumbs -

private void CellRightThumbDragDelta(object sender, DragDeltaEventArgs e)
{
    Thumb thumb = sender as Thumb;
 
    if (thumb != null)
    {
        // Get the current cell
        DataGridCell cell = thumb.Tag as DataGridCell;
        if (cell != null)
        {
            // Change the width of current cell's column
            cell.Column.Width = cell.Column.ActualWidth + e.HorizontalChange;
        }
    }
}

So we just need to update the width of column in which current cell resides.

private void CellBottomThumbDragDelta(object sender, DragDeltaEventArgs e)
{
    Thumb thumb = sender as Thumb;
 
    if (thumb == null)
    {
        return;
    }
 
    DataGridRow row = thumb.Tag as DataGridRow;
    if (row == null)
    {
        return;
    }
 
    DataGridCellsPresenter cellsPresenter = GetFirstVisualChild<DataGridCellsPresenter>(row);
    if (cellsPresenter == null)
    {
        return;
    }
 
    double newHeight = cellsPresenter.ActualHeight + e.VerticalChange;
 
    DataGridRowHeader rowHeader = GetFirstVisualChild<DataGridRowHeader>(row);
    if (rowHeader != null)
    {
        // clamp the CellsPresenter size to the RowHeader size or 
// MinHeight because the header wont shrink any smaller.
        double minHeight = Math.Max(rowHeader.DesiredSize.Height, MinHeight);
        if (newHeight < minHeight)
        {
            newHeight = minHeight;
        }
 
        // clamp the CellsPresenter size to the MaxHeight of Row, 
// because row wouldn't grow any larger
        double maxHeight = row.MaxHeight;
        if (newHeight > maxHeight)
        {
            newHeight = maxHeight;
        }
    }
 
    cellsPresenter.Height = newHeight;
 
    // Updating row's height doesn't work correctly; shows weird behavior
    // row.Height = newHeight >= 0 ? newHeight : 0;
}

Notice that simply setting the height of the current row is not the correct solution, I was doing this in the beginning and noticed some weird behavior (Ref. this SO thread); When there was no response to the problem I decided to take the hard path and look into the code of DataGrid available on CodePlex. Obviously it was not easy to dig into the code and find the root cause of the problem, so I decided to reproduce the problem and debug the code; but to my surprise there was no sample for DataGrid in the WPFToolkitSamples project of WPFToolkit. That’s weird, you have samples for various other controls but not for DataGrid ; anyways I added the sample I had posted on SO and removed some projects to successfully build the code. After doing some debugging I reached the code where rows were resized (through Row header gripper) and found that to change the height of rows, height of DataGridCellsPresenter was changed. I was changing the height of the row but DataGrid changes the height of DataGridCellsPresenter which was causing the inconsistent behavior; DataGridCellsPresenter  is an internal control used by DataGrid for generating the cells, following is a comment from DataGridCellsPresenter class on its purpose -

///   A control that will be responsible for generating cells.
///   This control is meant to be specified within the template of a DataGridRow.
///   The APIs from ItemsControl do not match up nicely with the meaning of a
///   row, which is why this is being factored out.

2 Comments

Add a Comment

As it will appear on the website

Not displayed

Your website