6

I am using a DataGrid in WPF and want it to shrink to only fit the width of its columns. It does this nicely for the initial rendering. When I resize a column to make it wider the grid grows as well. But if I resize the column to make it narrower again I get white space on the right side of my column (and I can see that the column header grey area is extended beyond the columns.

I would like to have the data grid shrink its width with the columns so I don't get the white space on the right. I have tried to debug the code and as far as I can see the problem is in the DataGridCellsPanel, but I can't see anyplace to fix the width measurement.

Any help would be appreciated.

jjrdk
  • 1,882
  • 15
  • 18

3 Answers3

3

I had that problem to a while back and I was getting so annoyed by it that I made an ugly fix for it. It's not pretty, but it gets the job done. First, this is only a problem when the Horizontal ScrollBar is invisible so we're gonna need a reference to it. This code will have to be run once all DataGridColumns have been loaded (in my case, all in Xaml, so the Loaded event) and it doesn't take adding/removing of DataGridColumns into consideration but that's an easy fix.

<DataGrid Name="c_dataGrid"
          Loaded="c_dataGrid_Loaded"
          ...>
    <DataGrid.Columns>
        <DataGridTextColumn ..."/>
        <DataGridTextColumn ..."/>
        <!-- ... -->

Then in the Loaded EventHandler we get the DataGrid ScrollViewer and add a listener for changes in the ActualWidthProperty of every DataGridColumn in the DataGrid.

private ScrollViewer m_dataGridScrollViewer = null;
private void c_dataGrid_Loaded(object sender, RoutedEventArgs e)
{
    m_dataGridScrollViewer = GetVisualChild<ScrollViewer>(c_dataGrid);
    DependencyPropertyDescriptor dependencyPropertyDescriptor =
        DependencyPropertyDescriptor.FromProperty(DataGridColumn.ActualWidthProperty, typeof(DataGridColumn));
    if (dependencyPropertyDescriptor != null)
    {
        foreach (DataGridColumn column in c_dataGrid.Columns)
        {
            dependencyPropertyDescriptor.AddValueChanged(column, DataGridColumn_ActualWidthChanged);
        }
    }
}

And then we compute the size of the DataGrid from the size of all DataGridColumns and add a constant of 8.0 (which is the difference normally).

private void DataGridColumn_ActualWidthChanged(object sender, EventArgs e)
{
    if (m_dataGridScrollViewer != null)
    {
        if (m_dataGridScrollViewer.ComputedHorizontalScrollBarVisibility != Visibility.Visible)
        {
            double dataGridWidth = 8.0;
            foreach (DataGridColumn column in c_dataGrid.Columns)
            {
                dataGridWidth += column.ActualWidth;
            }
            c_dataGrid.Width = dataGridWidth;
        }
        else
        {
            c_dataGrid.Width = double.NaN;
        }
    }
}

If you come up with a better way of doing this then let me know :)

public static T GetVisualChild<T>(object parent) where T : Visual
{
    DependencyObject dependencyObject = parent as DependencyObject;
    return InternalGetVisualChild<T>(dependencyObject);
}
private static T InternalGetVisualChild<T>(DependencyObject parent) where T : Visual
{
    T child = default(T);

    int numVisuals = VisualTreeHelper.GetChildrenCount(parent);
    for (int i = 0; i < numVisuals; i++)
    {
        Visual v = (Visual)VisualTreeHelper.GetChild(parent, i);
        child = v as T;
        if (child == null)
        {
            child = GetVisualChild<T>(v);
        }
        if (child != null)
        {
            break;
        }
    }
    return child;
}
Fredrik Hedblad
  • 83,499
  • 23
  • 264
  • 266
  • That's a nice solution. I have tweaked it slightly so that it sets the MaxWidth property instead. This solves the problem of the grid expanding beyond the constraints of the visual parent. I also converted it into a behavior instead in order to encapsulate it better. – jjrdk Nov 16 '10 at 11:30
  • Also, if DataGridColumn_ActualWidthChanged is not being called, ensure that you use Microsoft.Windows.Controls.DataGridColumn and not the System.Windows.Controls.DataGridColumn when you're getting DependencyPropertyDescriptor. – Monsignor Feb 03 '11 at 11:40
2

That's a nice solution. I have tweaked it slightly so that it sets the MaxWidth property instead. This solves the problem of the grid expanding beyond the constraints of the visual parent. I also converted it into a behavior instead in order to encapsulate it better.

This is what I ended up with.

public class UpdateWidthOnColumnResizedBehavior : Behavior<DataGrid>
{
        private static readonly DependencyPropertyDescriptor Descriptor;

        static UpdateWidthOnColumnResizedBehavior()
        {
            Descriptor = DependencyPropertyDescriptor.FromProperty(DataGridColumn.ActualWidthProperty, typeof(DataGridColumn));
        }

        protected override void OnAttached()
        {
            base.OnAttached();

            AssociatedObject.Columns.CollectionChanged += OnColumnsCollectionChanged;

            foreach (var column in AssociatedObject.Columns)
            {
                AddListener(column);
            }
        }

        void OnColumnsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            switch (e.Action)
            {
                case NotifyCollectionChangedAction.Add:
                    foreach (var column in e.NewItems.OfType<DataGridColumn>())
                    {
                        AddListener(column);
                    }
                    break;
                case NotifyCollectionChangedAction.Remove:
                    foreach (var column in e.OldItems.OfType<DataGridColumn>())
                    {
                        RemoveListener(column);
                    }
                    break;
                case  NotifyCollectionChangedAction.Replace:
                    foreach (var column in e.NewItems.OfType<DataGridColumn>())
                    {
                        AddListener(column);
                    }
                    foreach (var column in e.OldItems.OfType<DataGridColumn>())
                    {
                        RemoveListener(column);
                    }
                    break;
            }
        }

        protected override void OnDetaching()
        {
            base.OnDetaching();

            foreach (var column in AssociatedObject.Columns)
            {
                RemoveListener(column);
            }
        }

        private void AddListener(DataGridColumn column)
        {
            Descriptor.AddValueChanged(column, ResizeGrid);
        }

        private void RemoveListener(DataGridColumn column)
        {
            Descriptor.RemoveValueChanged(column, ResizeGrid);
        }

        private void ResizeGrid(object sender, EventArgs e)
        {
            var columnsWidth = AssociatedObject.Columns.Sum(c => c.ActualWidth);
            AssociatedObject.MaxWidth = columnsWidth + 2;
            AssociatedObject.InvalidateMeasure();
        }
    }

I still have some things to iron out about width coordination of two grids, but it looks to work for one.

jjrdk
  • 1,882
  • 15
  • 18
0

there is seems to be a slight problem with both of your approaches. When I drag the left most column to the right, the whole grid is getting resized/ rolled inside (unfortunatly I don't have enough reputation to post the image).

So I have modified jjrdk ResizeGrid function, so it calculate the last column width and extends it all the way to the left. The grid HorizontalAlignment and HorizontalContentAlignment must be set to HorizontalAlignment.Stretch.

void ResizeGrid(object sender, EventArgs e) 
    {
         var scroll = ExTreeHelper.FindVisualChild<ScrollViewer>(AssociatedObject);

        if (scroll != null && null != AssociatedObject.Columns && AssociatedObject.Columns.Count > 0)
        {
            var lastColumn = AssociatedObject.Columns.Last();

            double dataGridWidth = AssociatedObject.Columns.Sum(c => c.ActualWidth) + 2.0;

            if (scroll.ComputedHorizontalScrollBarVisibility != Visibility.Visible)
            {
                RemoveListener(lastColumn);

                AssociatedObject.Columns.Last().Width =
                    AssociatedObject.Columns.Last().Width.DisplayValue + scroll.ViewportWidth - dataGridWidth;

                AssociatedObject.Width = dataGridWidth + scroll.ViewportWidth - dataGridWidth;

                AddListener(lastColumn);
            }
            else
            {
                AssociatedObject.HorizontalAlignment = HorizontalAlignment.Stretch;
                AssociatedObject.HorizontalContentAlignment = HorizontalAlignment.Stretch;

                AssociatedObject.Width = double.NaN;
            }
        }         }  

The only issue I have, is that the scroll bar is always there, even if all the columns has been fit.

There is still another issue, when all the columns are collapsed to the left, it starts flickering.

Is there anything that can be done, to really get rid of this white space?

Leon

Leon
  • 21
  • 4