Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Problem while scrolling merged Header Cells of a DataGridView

In my WinForm project I have a DataGridView control.
I achieved to merge the Header cells of 2 Columns. It is working fine but there is a problem when scrolling. The image below should explain it.
Can anyone help?

This is the code I use to merging the Column Header cells.
The indexes of the merged Columns are 20 and 21.

private void loadEvents()
{
    this.dgv_db_door.Paint += new PaintEventHandler(dgv_db_door_Paint);
    this.dgv_db_door.Scroll += new ScrollEventHandler(dgv_db_door_Scroll);
    this.dgv_db_door.ColumnWidthChanged += DataGridView1_ColumnWidthChanged;
    this.dgv_db_door.Resize += DataGridView1_Resize;
}

private void dgv_db_door_Paint(object sender, PaintEventArgs e)
{
     string doorCloser = "DOOR CLOSER";
    
     //Index numbers of merged columns ara 20 and 21;
     int mergedColumn1 = 20;
     int mergedColumn2 = 21;
     Rectangle r1 = dgv_db_door.GetCellDisplayRectangle(mergedColumn1, -1, true);
     int w2 = dgv_db_door.GetCellDisplayRectangle(mergedColumn2, -1, true).Width;
     r1.X += 1;
     r1.Y += 1;
     r1.Width = r1.Width + w2 - 2;
     r1.Height = r1.Height / 2 - 2;
     e.Graphics.FillRectangle(new SolidBrush(dgv_db_door.ColumnHeadersDefaultCellStyle.BackColor), r1);
    
    StringFormat format = new StringFormat();
    
    format.Alignment = StringAlignment.Center;
    format.LineAlignment = StringAlignment.Center;
    e.Graphics.DrawString(doorCloser, dgv_db_door.ColumnHeadersDefaultCellStyle.Font,
    new SolidBrush(dgv_db_door.ColumnHeadersDefaultCellStyle.ForeColor), r1, format);
                 
}
private void dgv_db_door_Scroll(object sender, ScrollEventArgs e)
{
    if (e.OldValue > e.NewValue)
    {
                
    }
    else
    {
        this.InvalidateHeader();
    }
}

private void DataGridView1_Resize(object sender, EventArgs e)
{
    this.InvalidateHeader();
}
    
private void DataGridView1_ColumnWidthChanged(object sender, DataGridViewColumnEventArgs e)
{
     this.InvalidateHeader();
}

private void InvalidateHeader()
{
     Rectangle rtHeader = this.dgv_db_door.DisplayRectangle;
     rtHeader.Height = this.dgv_db_door.ColumnHeadersHeight / 2;
    this.dgv_db_door.Invalidate(rtHeader);
}

enter image description here

like image 221
Gokhan Avatar asked Oct 24 '25 14:10

Gokhan


1 Answers

A few modifications to the drawing procedure:

  • The custom Header drawing Rectangle is sized calculating the Width of all the Columns in the specified Range.
  • The position of the drawing area is calculated using the left-most Column's position, the RowHeadersWidth and the current DataGridView.HorizontalScrollingOffset, which defines the horizontal scroll position of the client rectangle.
  • To draw the custom Header, a clipping region is created - using Graphics.SetClip() - to exclude the Row Header from the drawing area.
  • The custom Header is only rendered when visible on screen.
  • I'm using TextRenderer.DrawText instead of Graphics.DrawString(), since this is the method used by this control to render its content.
  • Hence, I'm calculating the height of the text bounds using TextRenderer.MeasureText()
  • TextFormatFlags.PreserveGraphicsClipping is used to instruct TextRenderer not to draw the text outside the clipping region of the Graphics object.

Note 1: I've added Double-Buffering to the DataGridView, to avoid any flickering when clicking the Header Cells. It may have an impact when the grid needs to render a high number of Rows.

Note 2: You can remove all those InvalidateHeader() calls, not needed here.

► This new behavior allows to reset the range of Columns to include in the custom Header and the Header's Text at any time (as shown in the visual example).


This is how it looks now:

DataGridView Custom Merged Header


using System.Reflection;

TextFormatFlags centerTopflags = 
    TextFormatFlags.HorizontalCenter | TextFormatFlags.Top | TextFormatFlags.PreserveGraphicsClipping;
string mergedHeaderText = string.Empty;
int[] mergedColumns = null; 

public void SomeForm()
{
    this.InitializeComponent();
    var flags = BindingFlags.Instance | BindingFlags.NonPublic;
    dgvTest.GetType().GetProperty("DoubleBuffered", flags).SetValue(dgvTest, true);
    mergedColumns = new int[] { 20, 21 };
    mergedHeaderText = "DOOR CLOSER"
}

private void dgv_db_door_Paint(object sender, PaintEventArgs e)
{
    var dgv = sender as DataGridView;
    var headerStyle = dgv.ColumnHeadersDefaultCellStyle;
    int colsWidth = -1;
    int colsLeft = 1;

    // Absolute Width of the merged Column range
    for (int i = 0; i < mergedColumns.Length; i++) {
        var col = dgv.Columns[mergedColumns[i]];
        colsWidth += col.Visible ? col.Width : 0;
    }

    // Absolute Left position of the first Column to merge
    if (mergedColumns[0] > 0) {
        colsLeft += dgv.Columns.OfType<DataGridViewColumn>()
            .Where(c => c.Visible).Take(mergedColumns[0]).Sum(c => c.Width);
    }

    // Merged Headers raw drawing  Rectangle
    var r = new Rectangle(
        dgv.RowHeadersWidth + colsLeft - dgv.HorizontalScrollingOffset, 2, 
        colsWidth, dgv.ColumnHeadersHeight);

    // Measure the Height of the text to render - no wrapping
    r.Height = TextRenderer.MeasureText(e.Graphics, mergedHeaderText, headerStyle.Font, r.Size, centerTopflags).Height;

    // Draw the merged Headers only if visible on screen
    if (r.Right > dgv.RowHeadersWidth || r.X < dgv.DisplayRectangle.Right) {
        // Clip the drawing Region to exclude the Row Header
        var clipRect = new Rectangle(
            dgv.RowHeadersWidth + 1, 0, 
            dgv.DisplayRectangle.Width - dgv.RowHeadersWidth, dgv.ColumnHeadersHeight);
        e.Graphics.SetClip(clipRect);

        using (var brush = new SolidBrush(headerStyle.BackColor)) e.Graphics.FillRectangle(brush, r);
        TextRenderer.DrawText(e.Graphics, mergedHeaderText, headerStyle.Font, r, headerStyle.ForeColor, centerTopflags);
        e.Graphics.ResetClip();
    }
}
like image 95
Jimi Avatar answered Oct 26 '25 03:10

Jimi



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!