C# – How to resize WPF DataGrid to fit its content

cdatagridlayoutwpf

The aim

I would like to set such size for the DataGrid (standard, from WPF) so all cells (text) would be fully visible. I have window with DockPanel, with DataGrid in it, so when I resize the window, all nested widgets (DockPanel and DataGrid) are resized accordingly.

Example (edit-1)

Let's say you have window, 100 pixels wide and you have DataGrid with one column, which cell is "the quick brown fox…" (400 pixels wide). Thus the DataGrid should be resized to 400 pixels (probably more, because of padding) and the Window should be resized to 400 pixels too (also more, because of padding).

I didn't find any standard method to do it (AFAIK WPF provides way to clip the content to desired width, my problem is exactly opposite), so I come up with such ugly workaround, which does not work too well.

The workaround

  1. iterate over DataGrid headers (assuming they are just strings) and compute width required for the text
  2. iterate over DataGrid rows per each column (assuming they are TextBlock or TextBox) and compute the maximum width required for the text — add horizontal paddings for TextBlock/TextBox and horizontal margins for DataGrid cell
  3. sum all differences between DataGrid ActualWidth for columns and the maximum width computed in (2)
  4. increase the window width by the difference computed in (3)

THE PROBLEM

I did several tests, and in some cases the computed width is too big (this is minor problem), for some cases is too small. The problem starts at its core procedure — computing the required width for TextBox/TextBlock, computed width is always 1 unit less than it should be (if I set the width to computed one, 1 pixel from text is always clipped).

So which factor I am ignoring here? Or maybe better — is there already some method to resize DataGrid to fit its content?

The code

Computing width required for text (here for TextBlock):

    public static double TextWidth(this TextBlock widget, string text)
    {
        var formattedText = new FormattedText(text, // can use arbitrary text
                                              System.Globalization.CultureInfo.CurrentCulture,
                                              widget.FlowDirection,
                                              widget.FontFamily.GetTypefaces().FirstOrDefault(),
                                              widget.FontSize, 
                                              widget.Foreground);

        return formattedText.Width+widget.Padding.Left+widget.Padding.Right;
    }

Adjusting the Window size to fit DataGrid content (ugly_factor is ugly workaround 😉 since I didn't figure out how to fix it properly I set it to 1.3 and this way my window is "never" too small):

    public static void AdjustWidthToDataGrid(this Window window, DataGrid dataGrid, double ugly_factor)
    {
        var max_widths = dataGrid.Columns.Select(it => window.TextWidth(it.Header as string) 
                                                                        * ugly_factor).ToArray();

        foreach (var row in Enumerable.Range(0, dataGrid.Items.Count))
            foreach (var col in Enumerable.Range(0, dataGrid.Columns.Count))
            {
                var cell = dataGrid.GetCell(row, col);
                double width = 0;
                if (cell.Content is TextBlock)
                    width = (cell.Content as TextBlock).TextWidth();
                else if (cell.Content is TextBox)
                    width = (cell.Content as TextBox).TextWidth();

                if (cell.Content is FrameworkElement)
                {
                    var widget = cell.Content as FrameworkElement;
                    width = width + widget.Margin.Left + widget.Margin.Right;
                }

                max_widths[col] = Math.Max(max_widths[col], 
                                           width*ugly_factor+cell.Padding.Left+cell.Padding.Right);
            }

        double width_diff = 0;

        foreach (var col in Enumerable.Range(0, dataGrid.Columns.Count))
            width_diff += Math.Max(0,max_widths[col] - dataGrid.Columns[col].ActualWidth);

        if (width_diff > 0)
            window.Width = window.ActualWidth+ width_diff;
    }

Best Answer

I just came out of the same problem where I had to give options in a data grid's column to fit its width as per the content of both header and cell. I used the following code:

private void FitToContent()
    {
        // where dg is my data grid's name...
        foreach (DataGridColumn column in dg.Columns)
        {
            //if you want to size your column as per the cell content
            column.Width = new DataGridLength(1.0, DataGridLengthUnitType.SizeToCells);
            //if you want to size your column as per the column header
            column.Width = new DataGridLength(1.0, DataGridLengthUnitType.SizeToHeader);
            //if you want to size your column as per both header and cell content
            column.Width = new DataGridLength(1.0, DataGridLengthUnitType.Auto);
        }
    }

Also I provided an option on columns to fit as per the display (datagrid's width). for that I used the same code above with the following minor change:

column.Width = new DataGridLength(1.0, DataGridLengthUnitType.Star);

FOR ALL COLUMNS : Make sure you keep HorizontalScrollVisibility to Auto.