Scott Hanselman

How do I automatically size (autosize) Columns in a WinForms DataGrid?

June 17, '04 Comments [19] Posted in Programming
Sponsored By

Apparently this is, like, the problem for the ages or something, but noone on this planet has come up with a way to automatically size the columns in a DataGrid.  Sure, there's all sorts of pyscho ways that involve measuring the length of strings in DataSets with the Graphics context, yada yada.  But, since I'm binding strongly-typed Object Collections to DataGrids in WinForm apps, that doesn't work for me (and it's a little over the top, IMHO).

So, I thought about it like this:

  • If you double click on the little splitter between columns they will autosize.
  • Therefore, the code to autosize has been written for me; no need to measure strings, etc.
  • How do I force a double click? No, wait, wrongheadedness, how do I call whatever THEY call when a double click happens?
  • So, I reflectored into DataGrid.OnMouseDown and saw their custom HitTest calls a private ColAutoResize.  Ah, bingo.

If you're going to 'sin' do it with style - do it with Reflection.

private void dgLogging_DataSourceChanged(object sender, System.EventArgs e)
        {
            try
            {
                Type t = dgLogging.GetType();
                MethodInfo m = t.GetMethod("ColAutoResize",BindingFlags.NonPublic);

                for (int i = dgLogging.FirstVisibleColumn; (i < dgLogging.VisibleColumnCount); i++)
                {
                    m.Invoke(dgLogging, new object[]{i});
                }
            }
            catch (Exception ex)
            {
                System.Diagnostics.Trace.Write("Failed Resizing Columns: " + ex.ToString());
            }
        }

(Thanks to Patrick Cauldwell for his help)

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook twitter subscribe
About   Newsletter
Sponsored By
Hosting By
Dedicated Windows Server Hosting by ORCS Web
Thursday, June 17, 2004 10:33:32 PM UTC
Scott, now that is just awesome! Not so much the tip of how to resize the columns, but the idea of how you came up with a better way to do it. All about the way we think and come up with good/simple code & solutions.

Great idea - thanks for sharing.
Friday, June 18, 2004 12:38:24 AM UTC
I thought you could set the Width property of a custom column style to -1 to achieve the same effect...

(2 minutes later) I just tried that and it doesn't work. Maybe I'm confusing this with the asp.net grid control...
Stéphane Lajoie
Friday, June 18, 2004 1:30:10 AM UTC
Guess what you find if you look at ColAutoResize in Relector? Graphics.MeasureString. Most of the example tend to assume the DataSource is a DataTable, but tt works well with collections if you check for the IList or IListSource interface on the DataSource.
Friday, June 18, 2004 3:47:23 AM UTC
Awesome, Scott! That is a nice little snippet, even with Scott Allen's discovery. :)
Friday, June 18, 2004 4:41:38 AM UTC
Yes, of course in my reflectoring I saw that internally it was doing evil things, but my point was (I did a lousy job of explaining it) that ALL the code samples I saw on the net went ON and ON about the graphics stuff, many in DIFFERENT ways from the internal way.

My point is that - I knew the code was written already because the functionality existed if you double clicked. So, I just needed to figure out how to get access to it - regardless of whether they did the same stuff internally.

You are right though, that my object collections implemented IList!
Scott Hanselman
Friday, June 18, 2004 1:37:19 PM UTC
That was a great example of how a developer should think. Of course I also don't have a comforing feeling when I know I'm using some unintended, private functionality, but the process of getting to the solution was perfect.
I want to be like you when I grow up :)
Sergio Pereira
Friday, June 18, 2004 5:36:29 PM UTC
Agreed. And I also get your point that it's better to "illegally" use reflection than to go around measuring strings and duplicating work (possibly incorrectly) that's already done.

On a related note, I've always hated the ListView control in Windows. It should be much more intelligent than this when being resized. For example, when it grows beyond the last column in width, it just shows wasted blank space, even if some columns are too narrow for their content... I mean, we have web browsers doing a fine job laying out multiple levels of nested tables containing a mix of rich text and images, while desktop applications continue to use this relic from 15 years ago...

The new fancy layout stuff in VS 2005 might allow for a neat new list control. I guess I could try to make one, if I had a spare computer to install this "community preview" thing :).
Stephané
Wednesday, July 07, 2004 12:40:01 PM UTC
I'm trying to convert this to vb (and I've never done reflection before) and I'm having a problem. Is this a correct implementation of your code or am I going a little wrong here?

Public Class DataGridButBetter
Inherits DataGrid
Public Sub AutoResizeColumns()
Try
Dim t As Type = MyBase.GetType 'also tried me.GetType but t always ends up as DataGridButBetter
Dim m As System.Reflection.MethodInfo = t.GetMethod("ColAutoResize", System.Reflection.BindingFlags.NonPublic) 'and this ends up as nothing (null to you c# boys)
For i As Integer = Me.FirstVisibleColumn To Me.VisibleColumnCount - 1
m.Invoke(Me, New Object() {i})
Next
Catch ex As Exception
System.Diagnostics.Trace.Write("Failed Resizing Columns: " + ex.ToString())
End Try
End Sub
End Class

any ideas where I'm going wrong? thanks!
H McIntyre
Wednesday, July 07, 2004 5:55:11 PM UTC
Forgive me, but you are doing this to a WINFORM data grid, right?
Scott Hanselman
Thursday, July 08, 2004 7:30:48 AM UTC
Windows.Forms.DataGrid is what I'm doing this to.
h McIntyre
Friday, July 09, 2004 9:33:43 PM UTC
Excellent demonstration on reflection for someone new to reflection! I seem to be having reflection security problems though. This is a WINFORM application and when I debug it t.GetMethod("ColAutoResize", System.Reflection.BindingFlags.NonPublic) returns null. Is there anything special that needs to be done before the CLR will allow visibilty to the private methods? Thanks!
Doug Lott
Thursday, August 05, 2004 4:04:37 PM UTC
Great Job. For the last couple questions about the GetMethod returning null, try this:

[VB.net]
Dim m As MethodInfo = t.GetMethod("ColAutoResize", BindingFlags.NonPublic Or BindingFlags.Instance)

[C#]
MethodInfo m = t.GetMethod("ColAutoResize", BindingFlags.NonPublic | BindingFlags.Instance)

Someone else can probably explain why this works better than I.

Cheers!
John Reber
Thursday, September 02, 2004 8:41:05 PM UTC
h McIntyre - aren't you really doing it to a 'DataGridButBetter' extension of the DataGrid?? I try the same thing in my extended DataGrid and get null as well, even with that last OR addition. I think it is because we aren't getting the correct type back for GetType - we are getting our DataGrids, not the WinForms DataGrid...

Any ideas???
Andrew Kennett
Monday, September 20, 2004 4:44:55 PM UTC
I found, for whatever reason, that multiple queries to the VisibleColumnCount were not always the same...And it was stopping after 5 columns, regardless of how many were in the DataSet; however, the columns were there, and visible, just not being displayed.

[vb.net that's working great]
Private Sub sqlGrid_DataSourceChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles sqlGrid.DataSourceChanged
Try
Dim t As Type = sqlGrid.GetType
Dim m As Reflection.MethodInfo = t.GetMethod("ColAutoResize", Reflection.BindingFlags.NonPublic Or Reflection.BindingFlags.Instance)

Dim i As Integer = sqlGrid.FirstVisibleColumn
Dim j As Integer = sqlGrid.VisibleColumnCount
Do While i < j
m.Invoke(sqlGrid, New Object() {i})
i = i + 1
Loop
Catch ex As Exception
Trace.Write("Failed to resize column: " + ex.ToString + vbCrLf)
End Try
End Sub
opello
Friday, September 24, 2004 11:37:01 PM UTC
Reflection works on a specific Type so what you want to do in the subclassed DataGrid scenario is grab the MethodInfo of ColAutoResize(int) off DataGrid and *not* the subclass. You also need to make sure you're specifying BindingFlags.Instance as well as the parameter signature. You then perform Invoke() on your subclassed instance. Here's what it might look like in a sub-classed SuperDataGrid:

public void AutoResizeColumns() {

MethodInfo m = typeof(DataGrid).GetMethod("ColAutoResize", BindingFlags.Instance | BindingFlags.NonPublic, null, new Type[] { typeof(Int32) }, null);

for (int i = this.FirstVisibleColumn; (i < this.VisibleColumnCount); i++) {
m.Invoke(this, new object[] {i});
}
}
Tuesday, November 09, 2004 5:14:16 AM UTC
I found the following to be required:

using System.Reflection;



protected void dataGrid1_DataSourceChanged(object sender, System.EventArgs e)

{

try

{

Type t = dataGrid1.GetType();

MethodInfo m = t.GetMethod("ColAutoResize",

BindingFlags.Instance | BindingFlags.NonPublic);



for (int i = dataGrid1.FirstVisibleColumn;

i < dataGrid1.VisibleColumnCount; i++)

m.Invoke(dataGrid1, new object[]{i});

}

catch (Exception ex)

{

System.Diagnostics.Trace.Write("Failed Resizing Columns: " + ex.ToString());

}

}



”You must specify Instance or Static along with Public or NonPublic or no members will be returned”; quote from http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpref/html/frlrfsystemreflectionbindingflagsclasstopic.asp
Peter Beckwith
Saturday, March 19, 2005 6:35:07 PM UTC
Great, really great job!

I have been searching for an answer for this for a while now, and I came across solutions of pages and pages of code.

This is simple and elegant.

Congratulations.

If it can be useful I modified the code like this, so I can use it from wherever in my project.

\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
Public Sub columns_autoresize(ByRef sqlGrid As DataGrid)

Try
Dim t As Type = sqlGrid.GetType
Dim m As Reflection.MethodInfo = t.GetMethod("ColAutoResize", Reflection.BindingFlags.NonPublic Or Reflection.BindingFlags.Instance)

Dim i As Integer = sqlGrid.FirstVisibleColumn
Dim j As Integer = sqlGrid.VisibleColumnCount
Do While i < j
m.Invoke(sqlGrid, New Object() {i})
i = i + 1
Loop
Catch ex As Exception
Trace.Write("Failed to resize column: " + ex.ToString + vbCrLf)
End Try
End Sub
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
Lukygrl
Thursday, March 31, 2005 4:54:39 PM UTC
Very usefull code! Great work.
Thanks to all.
Carlos.
Monday, October 17, 2005 6:24:43 PM UTC
I was using the following code (on Paint Event) to AutoSize Columns in my datagrid:


Code:

Type t = dgTC.DataSource.GetType();
MethodInfo m = t.GetMethod("ColAutoResize", BindingFlags.Instance | BindingFlags.NonPublic);
for (int i = dgTC.FirstVisibleColumn; (i< dgTC.VisibleColumnCount); i++)
{
m.Invoke(dgTC, new object[]{i});
}

But after adding custom tablestyle and column style I am getting following exception:

Code:

System.Reflection.TargetInvocationException: Exception has been thrown
by the target of an invocation.

---> System.InvalidOperationException: The '' DataGridColumnStyle cannot
be used because it is not associated with a Property or Column in the DataSource.

At: System.Windows.Forms.DataGridColumnStyle.CheckValidDataSource(CurrencyManager value)

At: System.Windows.Forms.DataGridColumnStyle.GetColumnValueAtRow
(CurrencyManager source, Int32 rowNum)
at System.Windows.Forms.DataGrid.ColAutoResize(Int32 col)
--- End of inner exception stack trace ---

at System.Reflection.RuntimeMethodInfo.InternalInvoke(Object obj,
BindingFlags invokeAttr, Binder binder, Object[] parameters,
CultureInfo culture, Boolean isBinderDefault, Assembly caller, Boolean
verifyAccess)

at System.Reflection.RuntimeMethodInfo.InternalInvoke(Object obj,
BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture,
Boolean verifyAccess)

at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags
invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
at System.Reflection.MethodBase.Invoke(Object obj, Object[] parameters)
at TotalControl.frmDataEntry.AutoSizeDataGrid() in c:\documents and settings\
MyComp\my documents\visual studio
projects\totalcontrol\frmdataentry.cs:line 699


Can someone help me with this? Thanks a lot in advance.
Rob
Comments are closed.

Disclaimer: The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.