Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / WinForms

DataGrid with Sizable Multiline Column Headers

4.90/5 (16 votes)
8 Mar 20063 min read 1   1.9K  
Allows user defined sizing of DataGrid column header height to support multi-line header text.

Sample Image - SizableColumnHdrDataGrid.jpg

Introduction

This article shows you how to set the height of your DataGrid headers and show multi-line header text. Easily!

Using the code

So, you want to adjust the size of your DataGrid column headers, or want to support multi-line column header text? Well, here you go…

  • Add a project reference to SizableColumnHeaderDataGrid.dll.
  • Drop an instance of the SizableColumnHeaderDataGrid onto your form or control.
  • Set the ColumnHeaderHeight property in the property editor (under the Layout group) or in code.
  • If you want your column headers to wrap, be sure to use “\n” somewhere in the column header text.

It will look and act just like a regular DataGrid, but look closely in the properties editor, and you will see an additional property named ColumnHeaderHeight. Set its value in pixels, and away you go.

You can also set the property at runtime, but if you do, be sure to then call Invalidate() on your DataGrid to force a repaint.

C#
// Add carriage returns to the column header text
col1.HeaderText = "Row Index\nmulitplied\nby 1";

// Apply new column header size
sizableColumnHeaderDataGrid1.ColumnHeaderHeight = 39;

// Must call invalidate in order to force
// (not necessary if you set the property in the designer)
sizableColumnHeaderDataGrid1.Invalidate();

You can download the DLL now and live happily ever after with your new toy, or continue reading for the How did they do that? stuff.

Points of Interest

I fooled around with the DataGrid and found that, if I changed the font, the header would resize itself. Okay, so I know the header is capable of resizing but cannot find any exposed way to manipulate it. I also found that adding carriage returns to my header text was acceptable, with the *slight* drawback that you can only see the first line of text...

I then used Lutz Roeder’s Reflector for .NET to poke around a bit in the System.Windows.Forms namespace, and found my way to the DataGrid.ComputeLayout method which uses the headerFontHeight to set the height of the ColumnHeaders rectangle as shown in the following code:

C#
private void ComputeLayout()
{
…
      int num4 = this.headerFontHeight + 6;
      if (this.layout.ColumnHeadersVisible)
      {
            Rectangle rectangle6 = data1.ColumnHeaders;
            rectangle6 = rectangle3;
            rectangle6.Height = num4;
            rectangle3.Y += num4;
            rectangle3.Height -= num4;
            data1.ColumnHeaders = rectangle6;
      }
…
}

Woohoo, I’m done!

Or so I thought. Unfortunately, both the ComputeLayout member and the headerFontHeight property are private.

So, how did I make it work? Enter System.Reflection.

I created a derived DataGrid class and overrode the OnPaint and OnLayout events – these are the two places that call ComputeLayout in the System.Windows.Forms.DataGrid class. (Reflector tip: if you right click on a method in Reflector, you can get a Callee Graph to find out who is using the selected member.)

I then used reflection at runtime to get the headerFontHeight private property, and reset its value before allowing the DataGrid to paint itself!

Here's the fun reflection stuff:

C#
using System.Windows.Forms;
using System.Diagnostics;
using System.Reflection;

...

private void setColumnHeaderSize()
{
    try
    {

        if (null != this)
        {

            //
            // Use reflection to get the private
            // class property 'headerFontHeight'
            //
            DataGrid myClass = new DataGrid();

            FieldInfo headerFontHeightFieldInfo =
              myClass.GetType().GetField("headerFontHeight",
              BindingFlags.NonPublic|BindingFlags.Instance);

            Int32 currentHeight =
              (Int32)headerFontHeightFieldInfo.GetValue(this);


            //
            // Use public property to redefine 'headerFontHeight'
            //
            if (ColumnHeaderHeight >= 0)
            {
                headerFontHeightFieldInfo.SetValue(this,
                                    ColumnHeaderHeight);
            }

        }

    }

    catch (Exception e)
    {
        System.Diagnostics.Debug.Assert(false, e.ToString());
        throw e;
    }
}

And here are the slightly less exciting calls to make it work:

C#
private Int32 columnHeaderHeight = 13;

[Browsable(true),
 Description("Sets the column header height in pixels"),
 Category("Layout"), DefaultValue(13)]
public Int32 ColumnHeaderHeight
{
    get
    {
        return columnHeaderHeight;
    }
    set
    {
        columnHeaderHeight = value;
        OnLayout(null);
    }
}

protected override void OnLayout(LayoutEventArgs levent)
{
    setColumnHeaderSize();
    base.OnLayout (levent);
}

protected override void OnPaint(PaintEventArgs e)
{
    setColumnHeaderSize();
    base.OnPaint (e);
}

Caveat Emptor

This code works using reflection to monkey with private members of the System.Windows.Forms.DataGrid class. I cannot guarantee that it will continue to work if Microsoft changes code in that class. I have tested this only against the .NET Framework 1.1.

Yeah that's cool, but what if it could…

Some fun improvements available for someone with more free time:

  1. Make the header text auto-wrap based on the width of the column.
  2. Make the column headers auto update their height to fit the entire header text, given the width of the column.
  3. Make the headers sizable with mouse drags (in design-time and run-time).

History

  • Version 1 - released 10/26/2005.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here