How do I automatically size (autosize) Columns in a WinForms DataGrid?
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());
}
}
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.
About Newsletter
(2 minutes later) I just tried that and it doesn't work. Maybe I'm confusing this with the asp.net grid control...
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!
I want to be like you when I grow up :)
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 :).
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!
[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!
Any ideas???
[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
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});
}
}
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
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
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
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.
Comments are closed.
Great idea - thanks for sharing.