How to display an image in a datagridview column header?

35,147

Solution 1

One way you can do this is to use the CellsPainting event to draw the bitmap for a particular header cell. Here is code that does this assuming the bitmap is in an imagelist.

//this.images is an ImageList with your bitmaps
void dataGridView1_CellPainting(object sender, DataGridViewCellPaintingEventArgs e)
{
    if (e.ColumnIndex == 1 && e.RowIndex == -1)
    {
        e.PaintBackground(e.ClipBounds, false);

        Point pt = e.CellBounds.Location;  // where you want the bitmap in the cell

        int offset = (e.CellBounds.Width - this.images.ImageSize.Width) / 2;
        pt.X += offset;
        pt.Y += 1;
        this.images.Draw(e.Graphics, pt, 0);
        e.Handled = true;
    }
}

Solution 2

I needed a bit more complex thing - adding image in front of text in some column headers with respect to column alignment.

You need to implement your own System.Windows.Forms.DataGridViewColumnHeaderCell and replace ColumnHeaderCell:

// Create header and set up image
YourDataGridViewColumnHeaderCell headerCell = new YourDataGridViewColumnHeaderCell();
headerCell.Image = something;

// Create column
DataGridViewColumn yourColumn = new DataGridViewTextBoxColumn(); 
// ...
yourColumn.ColumnHeaderCell = new headerCell;

Now the funny part (implementation of your column header):

class YourDataGridViewColumnHeaderCell : System.Windows.Forms.DataGridViewColumnHeaderCell
{
    // Set up image as you want
    System.Drawing.Image Image { get; set; }
}

Now we want to add Paint() method. The only tricky part is working with System.Windows.Forms.DataGridViewPaintParts.

protected override void Paint( Graphics graphics, Rectangle clipBounds, Rectangle cellBounds, int rowIndex, DataGridViewElementStates dataGridViewElementState,
    object value, object formattedValue, string errorText, DataGridViewCellStyle cellStyle, DataGridViewAdvancedBorderStyle advancedBorderStyle,
    DataGridViewPaintParts paintParts )
{
    // Outside header or without an image, use default painting
    if ((rowIndex != -1) || (Image == null)) {
        base.Paint( graphics, clipBounds, cellBounds, rowIndex, dataGridViewElementState, value, formattedValue, errorText, cellStyle, advancedBorderStyle, paintParts );
        return;
    }

    // Borders, background, focus selection can remain the same
    // But Foreground will have different image
    base.Paint( graphics, clipBounds, cellBounds, rowIndex, dataGridViewElementState, value, formattedValue, errorText, cellStyle,
        advancedBorderStyle, paintParts & ~DataGridViewPaintParts.ContentForeground );

    // Repainting of content background (that's where we want to place our image)
    if ((paintParts & DataGridViewPaintParts.ContentBackground) != DataGridViewPaintParts.None) {
        // +4 is hardcoded margin 
        Point bounds = new Point( cellBounds.X + 4, cellBounds.Y );

        // Handle vertical alignment correctly
        switch (cellStyle.Alignment) {
            // Top
            case DataGridViewContentAlignment.TopLeft:
            case DataGridViewContentAlignment.TopCenter:
            case DataGridViewContentAlignment.TopRight:
                // Already set
                break;

            // Middle
            case DataGridViewContentAlignment.MiddleLeft:
            case DataGridViewContentAlignment.MiddleCenter:
            case DataGridViewContentAlignment.MiddleRight:
                bounds.Y = cellBounds.Y + (cellBounds.Height - Image.Height) / 2;
                break;

            // Bottom
            case DataGridViewContentAlignment.BottomLeft:
            case DataGridViewContentAlignment.BottomCenter:
            case DataGridViewContentAlignment.BottomRight:
                bounds.Y = cellBounds.Y + (cellBounds.Height - Image.Height);
                break;

        }
        graphics.DrawImage( Image, bounds );
    }

    // Foreground should be shifted by left image margin + image.width + right 
    // image margin and of course target spot should be a bit smaller
    if ((paintParts & DataGridViewPaintParts.ContentForeground) != DataGridViewPaintParts.None) {
        Rectangle newCellBounds = new Rectangle( cellBounds.X + 4 + Image.Width + 4, cellBounds.Y, cellBounds.Width - Image.Width - 8, cellBounds.Height );
        base.Paint( graphics, clipBounds, newCellBounds, rowIndex, dataGridViewElementState, value, formattedValue, errorText, cellStyle,
            advancedBorderStyle, DataGridViewPaintParts.ContentForeground );
    }

}

If you want to use AutoSizeColumnsMode set to DataGridViewAutoSizeColumnsMode.ColumnHeaders (so you would autofit image and text) you need to override DataGridViewColumnHeaderCell.GetPreferredSize. I did this by using base implementation and adding Image.Width + Padding to it.

protected override Size GetPreferredSize( Graphics graphics, DataGridViewCellStyle cellStyle, int rowIndex,Size constraintSize )
{
    // Load up original image
    Size original = base.GetPreferredSize( graphics, cellStyle, rowIndex, constraintSize );

    // Ensure the image is set and that we are working on header
    if ((rowIndex != -1) || (Image == null)) {
        return original;
    }

    // -1 is reserved value
    if (original.Width < 0) {
        return original;
    }
    return new Size( original.Width + Image.Width + 4, original.Height );
}

NOTE: I've spent several hours digging in .NET sources until I figured this out. Hopefully you won't have to.

Solution 3

Try This:

Private Sub DataGridView1_CellPainting(ByVal sender As System.Object, ByVal e As System.Windows.Forms.DataGridViewCellPaintingEventArgs) Handles DataGridView1.CellPainting
        If e.RowIndex > -1 Then

            If e.ColumnIndex = -1 Then
                e.Paint(e.CellBounds, DataGridViewPaintParts.Focus And Not DataGridViewPaintParts.ContentForeground)
                DataGridView1.RowHeadersBorderStyle = DataGridViewHeaderBorderStyle.Single
                e.Graphics.DrawImage(IconImg, e.CellBounds)
                e.Handled = True
                'DataGridView1.RowHeadersWidth = 100
                'DataGridView1.ColumnHeadersHeight = 25
            End If

        End If
Share:
35,147
Admin
Author by

Admin

Updated on August 12, 2022

Comments

  • Admin
    Admin almost 2 years

    At run-time, I am adding a DataGridView to a windows form. The final column is a DataGridViewImageColumn:

    Dim InfoIconColumn As New DataGridViewImageColumn
    MyDataGridView.Columns.Insert(MyDataGridView.Columns.Count, InfoIconColumn)
    

    Adding the following code will get my Information Icon (bitmap) to display in each of the column cells but NOT the column header:

    Dim InfoIcon As New Bitmap("C:\MyPath\InfoIcon.bmp")
    InfoIconColumn.Image = InfoIcon
    

    Also, it is worth noting that the image displays 'perfectly' in the cells i.e. it is sized correctly to fit the cell.

    However, I cannot find a way to add the same image to the column header cell. After some googling I used the following code which placed the image in the header cell but left me with two problems:

    1. The image did not 'auto-size' to the column headercell in the same way it did when added to the column cells. The image was slightly larger and blurred.
    2. By using the _CellPainting event slowed down performance i.e. when hovering over the DataGridView to highlight the selected row the highlighting lagged behind where my mouse was placed.

    Here is the code:

    Private Sub MyDataGridView_CellPainting(ByVal sender As Object, ByVal e As System.Windows.Forms.DataGridViewCellPaintingEventArgs) Handles MyDataGridView.CellPainting
       Dim InfoIcon As Image = Image.FromFile("C:\MyPath\InfoIcon.bmp")
       If e.RowIndex = -1 AndAlso e.ColumnIndex = MyDataGridView.Columns.Count - 1 Then
           e.Paint(e.CellBounds, DataGridViewPaintParts.All And Not   DataGridViewPaintParts.ContentForeground)
           e.Graphics.DrawImage(InfoIcon, e.CellBounds)
           e.Handled = True
        End If
    End Sub
    

    Does anybody know of a way to solve my problem and get a nicely sized, sharp image into a DataGridViewImageColumn headercell at run-time?

  • RJ Lohan
    RJ Lohan about 12 years
    In the event you don't store the Image in an ImageList, modifying the 2nd last LOC to; e.Graphics.DrawImage(image, pt); also works.
  • Khurram Ali
    Khurram Ali about 9 years
    Can we add panel control just above the datagridview column headers ?
  • Vyktor
    Vyktor about 9 years
    @KhurramAli I've never tested/tried that... I think it should be possible... If you get to test it, please let me know whether it worked.
  • Jack
    Jack almost 9 years
    It works but how can I place the image by text's side with some padding?
  • ehh
    ehh about 6 years
    You should explain what you are doing. I was lucky that I tried your code since e.Paint(e.CellBounds, DataGridViewPaintParts.Focus And Not DataGridViewPaintParts.ContentForeground) was exactly what I was looking for.
  • user3700562
    user3700562 almost 6 years
    @Vyktor DataGridViewColumn has no such property ColumnHeaderCell. Can you clarify?
  • Vyktor
    Vyktor almost 6 years
    @user3700562 according to DataGridViewColumn the HeaderCell is Available since 2.0. But it also notes that it's [BrowsableAttribute(false)] so you are NOT able to see it in designer (but have to change it programmatically).