How to add different colour icons/images to the Row Header of a DataGridView

285 Views Asked by At

How to add different colour icons/images PNG Transparent to DataGridView row header in VB.NET?

Is it possible to provide a different color each row in the header row of DataGridView or is there another solution with DataGridView Column Image or else. is there any other solution just fill a different color only because my image file is Transparent png ?

Private Table1 As DataTable

Private Sub createdatatable()
    'Dim Table1 As DataTable
    Table1 = New DataTable("TableName")
    Dim column1 As DataColumn = New DataColumn("Column1")
    column1.DataType = System.Type.GetType("System.String")
    Dim column2 As DataColumn = New DataColumn("Column2")
    column2.DataType = System.Type.GetType("System.Int32")
    Dim column3 As DataColumn = New DataColumn("Column3")
    column3.DataType = System.Type.GetType("System.Int32")
    Table1.Columns.Add(column1)
    Table1.Columns.Add(column2)
    Table1.Columns.Add(column3)
    Dim Row1 As DataRow
    Dim Row2 As DataRow
    Dim Row3 As DataRow
    Row1 = Table1.NewRow()
    Row2 = Table1.NewRow()
    Row3 = Table1.NewRow()
    Row1.Item("Column1") = "Item1"
    Row1.Item("Column2") = 44
    Row1.Item("Column3") = 99
    Row2.Item("Column1") = "Item2"
    Row2.Item("Column2") = 50
    Row2.Item("Column3") = 70
    Row3.Item("Column1") = "Item3"
    Row3.Item("Column2") = 75
    Row3.Item("Column3") = 85
    Table1.Rows.Add(Row1)
    Table1.Rows.Add(Row2)
    Table1.Rows.Add(Row3)
    ' Repeat for other rows
End Sub
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
    createdatatable()
    DataGridView1.DataSource = Table1
End Sub

Private Sub DataGridView1_RowPostPaint(sender As Object, e As DataGridViewRowPostPaintEventArgs) Handles DataGridView1.RowPostPaint
    'Convert the image to icon, in order to load it in the row header column
    Dim myBitmap As New Bitmap(My.Resources.money)
    Dim myIcon As Icon = Icon.FromHandle(myBitmap.GetHicon())

    Dim graphics As Graphics = e.Graphics

    'Set Image dimension - User's choice
    Dim iconHeight As Integer = 14
    Dim iconWidth As Integer = 14

    'Set x/y position - As the center of the RowHeaderCell
    'INSTANT VB WARNING: Instant VB cannot determine whether both operands of this division are integer types - if they are then you should use the VB integer division operator:
    Dim xPosition As Integer = e.RowBounds.X + (DataGridView1.RowHeadersWidth / 2)
    Dim yPosition As Integer = e.RowBounds.Y + ((DataGridView1.Rows(e.RowIndex).Height - iconHeight) \ 2)

    Dim rectangle As New Rectangle(xPosition, yPosition, iconWidth, iconHeight)
    graphics.DrawIcon(myIcon, rectangle)

End Sub

Current result:
ICON IN ROW HEADER DATAGRIDVIEW

Image used:
icons/images PNG Transparent

The desired result of each image's different colors

The desired result of each image's different colors

2

There are 2 best solutions below

11
On BEST ANSWER

Consider implementing the CellPainting event instead to control which paint parts to paint before drawing anything else. The parts defined in the DataGridViewPaintParts enum.

Override the Onload method or implement the Load event to create the data source, bind the control, and create a color array.

Private colors As Color()

Protected Overrides Sub OnLoad(e As EventArgs)
    MyBase.OnLoad(e)

    colors = {Color.Red, Color.Green, Color.Orange, Color.Black}

    Dim Table1 = New DataTable("TableName")

    Table1.Columns.AddRange({
        New DataColumn("Column1", GetType(String)),
        New DataColumn("Column2", GetType(Integer)),
        New DataColumn("Column3", GetType(Integer))
    })

    Table1.Rows.Add("Item1", 44, 99)
    Table1.Rows.Add("Item2", 50, 70)
    Table1.Rows.Add("Item3", 75, 85)

    DataGridView1.RowTemplate.Height = 48
    DataGridView1.RowHeadersWidth = 48
    DataGridView1.DataSource = Table1
End Sub

In the CellPainting event, call the e.Paint(...) method and select which parts the base should paint, pick a color from the array to create a SolidBrush used to fill an ellipse as a background of your image. Lastly, draw the image within the ellipse rectangle.

For example:

Private Sub DataGridView1_CellPainting(
                sender As Object,
                e As DataGridViewCellPaintingEventArgs) _
                Handles DataGridView1.CellPainting
    If e.RowIndex >= 0 AndAlso e.ColumnIndex = -1 AndAlso
        e.RowIndex <> DataGridView1.NewRowIndex Then

        'Option 1: Resizable image. Zoom effect.
        'Dim sz = Math.Min(e.CellBounds.Width, e.CellBounds.Height) - 6
        'Dim ellipseRect = New Rectangle(
        '        e.CellBounds.X + (e.CellBounds.Width - sz) \ 2,
        '        e.CellBounds.Y + (e.CellBounds.Height - sz) \ 2,
        '        sz, sz)
        'Dim imgRect = Rectangle.Inflate(ellipseRect, -3, 3)


        'Option 2: Fixed size. Change as needed.
        'Dim ellipseSize = 32            
        'Dim ellipseRect = New Rectangle(
        '    e.CellBounds.X + (e.CellBounds.Width - ellipseSize) \ 2,
        '    e.CellBounds.Y + (e.CellBounds.Height - ellipseSize) \ 2,
        '    ellipseSize, ellipseSize)
        'Dim imgSize = 24 ' or = ellipseSize
        'Dim imgRect = New Rectangle(
        '    ellipseRect.X + (ellipseRect.Width - imgSize) \ 2,
        '    ellipseRect.Y + (ellipseRect.Height - imgSize) \ 2,
        '    imgSize, imgSize)


        'Option 3: Size based on the RowTemplate.Height property.
        Dim ellipseSize = DataGridView1.RowTemplate.Height - 3
        Dim ellipseRect = New Rectangle(
                e.CellBounds.X + (e.CellBounds.Width - ellipseSize) \ 2,
                e.CellBounds.Y + (e.CellBounds.Height - ellipseSize) \ 2,
                ellipseSize, ellipseSize)
        Dim imgSize = ellipseSize ' Or smaller...
        Dim imgRect = New Rectangle(
            ellipseRect.X + (ellipseRect.Width - imgSize) \ 2,
            ellipseRect.Y + (ellipseRect.Height - imgSize) \ 2,
            imgSize, imgSize)
        Dim colorIndex = e.RowIndex Mod colors.Length
        Dim g = e.Graphics
        Dim gs = g.Save()

        e.Paint(e.ClipBounds, DataGridViewPaintParts.Background Or
                DataGridViewPaintParts.Border Or
                DataGridViewPaintParts.SelectionBackground)

        Using bmp = My.Resources.Dollar,
            ellipseBrush = New SolidBrush(colors(colorIndex))
            g.SmoothingMode = SmoothingMode.AntiAlias
            g.FillEllipse(ellipseBrush, ellipseRect)
            g.InterpolationMode = InterpolationMode.HighQualityBicubic
            g.DrawImage(bmp, imgRect, 0, 0, bmp.Width, bmp.Height,
                        GraphicsUnit.Pixel)
        End Using

        g.Restore(gs)
        e.Handled = True
    End If
End Sub

76570839

5
On

You should subscribe to the CellPainting event instead of RowPostPaint for this, the latter may cause unwanted flickering.
CellPainting also allows more control on what is painted. For example, you could decide that you don't want to paint the arrow that identifies the current Row, or replace it with something else, calling or not e.PaintContent()

As a note, don't use a Resource object directly, assign it to local object instead.
The Project's Resources is a factory, it creates a new object each time you ask for one

You didn't specify the criteria used to select the background Color, here I'm using a fixed color, change as needed

Private table1 As DataTable = CreateDataTable()

Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
    DataGridView1.DataSource = table1
    ' Adjust the size of the image as required
    Dim rowIconSize = New Size(DataGridView1.RowTemplate.Height - 2, DataGridView1.RowTemplate.Height - 4)
    DataGridViewIcon.Image = New Bitmap(My.Resources.money)
    DataGridViewIcon.ImageSize = rowIconSize
End Sub

Private Sub DataGridView1_CellPainting(sender As Object, e As DataGridViewCellPaintingEventArgs) Handles DataGridView1.CellPainting
    Dim dgv = CType(sender, DataGridView)
    If dgv.RowHeadersVisible Then
        If e.ColumnIndex >= 0 OrElse e.RowIndex < 0 OrElse e.RowIndex = dgv.NewRowIndex Then Return
        e.PaintBackground(e.ClipBounds, True)
        ' Uncomment if you want to paint the current Row's arrow
        'e.PaintContent(e.ClipBounds)
        DataGridViewIcon.DrawImage(e.Graphics, e.CellBounds, Color.Purple)
        e.Handled = True
    End If
End Sub

The DataGridViewIcon class stores the Image reference and the base Size used to render the bitmap; it can be changed if needed
Its DrawImage() method uses the Graphics object passed to render the image at the center of bounds specified, superimposing the Image on an Ellipse filled with the specified color

Friend Class DataGridViewIcon
    Public Shared Property ImageSize As Size = Size.Empty
    Public Shared Property Image As Bitmap = Nothing

    Public Shared Sub DrawImage(g As Graphics, bounds As Rectangle, backColor As Color)
        If Image Is Nothing OrElse ImageSize = Size.Empty Then Return
        Dim imagePos = New Point(
        bounds.X + (bounds.Width - ImageSize.Width) \ 2,
        bounds.Y + (bounds.Height - ImageSize.Height) \ 2)
        Dim imageRect = New Rectangle(imagePos, New Size(ImageSize.Width, ImageSize.Height))

        g.SmoothingMode = SmoothingMode.AntiAlias
        g.InterpolationMode = InterpolationMode.HighQualityBicubic

        Using brush As New SolidBrush(backColor)
            g.FillEllipse(brush, imageRect)
        End Using
        g.DrawImage(Image, imageRect)
        g.SmoothingMode = SmoothingMode.Default
    End Sub
End Class

The DataTable creation needs some refactoring and make the method return an object you can assign to a Field or whatever:

Private Function CreateDataTable() As DataTable
    Dim dt As New DataTable()
    Dim column1 As DataColumn = New DataColumn("Column1", GetType(String))
    Dim column2 As DataColumn = New DataColumn("Column2", GetType(Integer))
    Dim column3 As DataColumn = New DataColumn("Column3", GetType(Integer))

    dt.Columns.AddRange({column1, column2, column3})
    dt.Rows.Add("Item1", 44, 99)
    dt.Rows.Add("Item2", 50, 70)
    dt.Rows.Add("Item3", 75, 85)

    Return dt
End Function