OdeToCode IC Logo

Auto Resizing Columns In A Windows Form DataGrid

Sunday, December 28, 2003

At one point in the beta for .NET, I am sure I remember the DataGrid having an auto size property of some sort. This made all of the columns of a DataGrid wide enough to display the full text for every cell it contains. I am also sure this functionality is not in the release version. I can’t think of a reason why it might have been removed, except perhaps it was a performance and resource drain. Nevertheless, I know people need this functionality – me for one. One solution could look like the following code.

protected void SizeColumns(DataGrid grid)
{
  Graphics g = CreateGraphics();  

  DataTable dataTable = (DataTable)grid.DataSource;

  DataGridTableStyle dataGridTableStyle = new DataGridTableStyle();

   dataGridTableStyle.MappingName = dataTable.TableName;

  foreach(DataColumn dataColumn in dataTable.Columns)
  {
    int maxSize = 0;

    SizeF size = g.MeasureString(
                    dataColumn.ColumnName,
                    grid.Font
                 );

     if(size.Width > maxSize)
      maxSize = (int)size.Width;

    foreach(DataRow row in dataTable.Rows)
    {
      size = g.MeasureString(
                row[dataColumn.ColumnName].ToString(),
                grid.Font
          );

       if(size.Width > maxSize)
        maxSize = (int)size.Width;
    }

    DataGridColumnStyle dataGridColumnStyle =  new DataGridTextBoxColumn();
    dataGridColumnStyle.MappingName = dataColumn.ColumnName;
    dataGridColumnStyle.HeaderText = dataColumn.ColumnName;
    dataGridColumnStyle.Width = maxSize + 5;
    dataGridTableStyle.GridColumnStyles.Add(dataGridColumnStyle);
  }
  grid.TableStyles.Add(dataGridTableStyle);          

  g.Dispose();
}

If you are binding to an ArrayList or other custom collection implementing IList, try the more generic code below. I'd like to hear if it works or doesn't work for you. I've tested the code with DataTable and ArrayList DataSources, and learned quite a bit about Windows forms data binding.

I implemented this method in a class derived from DataGrid because originally I was counting on getting to some protected members of DataGrid for help, but as it turns out the whole process completes just using reflection on the DataSource. After reading some documentation and looking at the DataGrid with the Reflector tool, I'm pretty sure this is what the DataGrid does under the covers in any case.

The last item I needed to figure out was how to determine the MappingName property. The very next day a Chris Sells blog entry provides the answer from inside Microsoft.

public void AutosizeColumns()
{
   IList list = null;
   if(DataSource is IList)
   {
      list = (IList)DataSource;
   }
   else if(DataSource is IListSource)
   {
      list = ((IListSource)DataSource).GetList();
   }     

   if(list == null || list.Count < 0)
   {
      return;
   }
   
   PropertyDescriptorCollection pdc = TypeDescriptor.GetProperties(list[0]);	
   DataGridTableStyle dataGridTableStyle = new DataGridTableStyle();         
   dataGridTableStyle.MappingName = GetMappingName(list);

   using(Graphics g = CreateGraphics())
   {
      for(int i = 0; i < pdc.Count; i++)
      {
         SizeF maxSize = g.MeasureString(pdc[i].DisplayName, Font);
        
         foreach(object o in list)
         {
            object result = pdc[i].GetValue(o);
            string value = pdc[i].Converter.ConvertToString(result);

            SizeF size = g.MeasureString(value, Font);
            if(size.Width > maxSize.Width)
               maxSize = size;
         }

         DataGridColumnStyle dataGridColumnStyle = new DataGridTextBoxColumn(
         dataGridColumnStyle.MappingName = pdc[i].Name;
         dataGridColumnStyle.HeaderText = pdc[i].DisplayName;
         dataGridColumnStyle.Width = (int)(maxSize.Width + 5);
         dataGridTableStyle.GridColumnStyles.Add(dataGridColumnStyle);
      }
      TableStyles.Add(dataGridTableStyle);
   }
}

protected string GetMappingName(IList list)
{
   string result;

   if(list is ITypedList)
   {
      result = ((ITypedList)list).GetListName(null);
   }
   else
   {
      result = list.GetType().Name;
   }

   return result;
}