Creating A Multiple Column Header Within DataGridView


The other day I had a need to create a grid with a column header that spanned four columns. Having done a lot of ASP.Net in the past (and HTML directly in the more distant past), and knowing that this is a very simple thing to do in that environment, I figured it would be just as simple in the WinForms environment using the DataGridView. As happens far more often than I'd like to admit, I was wrong, although not by much. It wasn't as simple as I had hoped, but it really wasn't all that bad. Read on:

Basically, all we need to do is handle the DataGridView's Paint() event. Let's say we want to span columns 3, 4, 5, and 6 with one header that says "My Big Header". Within the Paint() event handler, we'll get a handle to the header cell of the first column you wish to span ("Column 3"), and we'll use that to determine the coordinates of the location on the grid at which we will begin our header text: 

DataGridViewCell hc = grid1.Columns["Column 3"].HeaderCell;
Rectangle hcRct = grid1.GetCellDisplayRectangle(hc.ColumnIndex, -1, true);
 
The first line of code should be self-explanatory. In the second line, we're getting a Rectangle object based on the cell in question. Passing the column index and the row index ("-1" means the row header) to the DataGridView's GetCellDisplayRectangle() method gets us there.

Next, we'll use that Rectangle object to get another Rectangle that represents the entire area to be covered by our new Multi-Column-Header. We can do that like so: 

            int multiHeaderWidth = grid1.Columns[hc.ColumnIndex].Width + grid1.Columns[hc.ColumnIndex + 1].Width + grid1.Columns[hc.ColumnIndex + 2].Width + grid1.Columns[hc.ColumnIndex + 3].Width;

            Rectangle headRct = new Rectangle(hcRct.Left, hc.ContentBounds.Y + 2, multiHeaderWidth, grid1.ColumnHeadersHeight);

            headRct.Height -= 3;
 
As you can see, the first line above simply gets the total width of all four columns to be spanned. We then pass that variable along with the other three coordinates (left, top, and height) required to instantiate a Rectangle object. (We add 2 to the Y coordinate and subtract 3 from the Rectangle's height in the next line to create a margin, so that our header cell's top and bottom borders are still visible.)

Next we need to find the size our string will be based on its length and the font we'll be using: 

SizeF sz = e.Graphics.MeasureString("My Big Header", grid1.Font);
 
Then we figure out where the top will need to be in order to make it vertically centered: 

int headerTop = Convert.ToInt32((headRct.Height / 2) - (sz.Height / 2)) + 2;
 
Then we set the background color to match the grid's header color: 

e.Graphics.FillRectangle(new SolidBrush(SystemColors.Control), headRct);
 
And finally, we draw the text, starting 2 pixels to the right of the left-most point of our rectangle so it looks nice: 

e.Graphics.DrawString("My Big Header", grid1.ColumnHeadersDefaultCellStyle.Font, Brushes.Black, hcRct.Left + 2, headerTop); 

Putting it all together, here's our Paint() event handler: 

 private void grid1_Paint(object sender, PaintEventArgs e)
 {
    DataGridViewCell hc = grid1.Columns["Column 3"].HeaderCell;
    Rectangle hcRct = grid1.GetCellDisplayRectangle(hc.ColumnIndex, -1, true);

    int multiHeaderWidth = grid1.Columns[hc.ColumnIndex].Width + grid1.Columns[hc.ColumnIndex + 1].Width + grid1.Columns[hc.ColumnIndex + 2].Width + grid1.Columns[hc.ColumnIndex + 3].Width;
    Rectangle headRct = new Rectangle(hcRct.Left, hc.ContentBounds.Y + 2, multiHeaderWidth, grid1.ColumnHeadersHeight);
    headRct.Height -= 3;

    SizeF sz = e.Graphics.MeasureString("My Big Header", grid1.Font);
    int headerTop = Convert.ToInt32((headRct.Height / 2) - (sz.Height / 2)) + 2;
    e.Graphics.FillRectangle(new SolidBrush(SystemColors.Control), headRct);
    e.Graphics.DrawString("My Big Header", grid1.ColumnHeadersDefaultCellStyle.Font, Brushes.Black, hcRct.Left + 2, headerTop);
 }

And here's a screenshot:

image2.gif

Cool, huh? Oh, and if you're wondering how I created those cells that contain both checkboxes and text, take a look at my article entitled, Extending The DataGridViewCheckBoxCell To Include Text.

Hope this helped. Enjoy!

Dave Verschleiser