1

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

dr.null
  • 4,032
  • 3
  • 9
  • 12
roy
  • 693
  • 2
  • 11
  • You're almost there! The `RowPostPaint` is an event that gets called for each row. So what you need to do is create an Bitmap[4] array with 1,2,3 bitmaps and then change this line to load them: `Dim myIcon As Icon = Icon.FromHandle(myBitmap[e.RowIndex % 3].GetHicon())` – Jeremy Thompson Jun 28 '23 at 07:37
  • @JeremyThompson , thanks for your response but I replace with your code there is an error `Value of type 'Bitmap' cannot be converted to 'IntPtr' , Character is not valid, Comma, ')', or a valid expression continuation expected` – roy Jun 28 '23 at 07:48
  • @JeremyThompson, you seem to have mixed VB and C# syntax. I think that should be `Dim myIcon As Icon = Icon.FromHandle(myBitmap(e.RowIndex Mod 3).GetHicon())`. – jmcilhinney Jun 28 '23 at 08:09
  • Note that `myBitmap` is type `Bitmap` in your question. In the suggested modification, you were instructed to create a `Bitmap` array and it is that array that `myBitmap` would be referring to in the suggested modification. – jmcilhinney Jun 28 '23 at 08:10
  • @jmcilhinney , Thank you reply from your code but there is an error `Error BC30367 Class 'Bitmap' cannot be indexed because it has no default property.` – roy Jun 28 '23 at 08:17
  • Yeah, I just addressed that in the comment above yours. You were told to create an array so create one, then index that. – jmcilhinney Jun 28 '23 at 08:19
  • @jmcilhinney , Please guide me to create a bitmap array as per your recommendation – roy Jun 28 '23 at 08:32
  • @JeremyThompson , `You're almost there! The RowPostPaint is an event that gets called for each row. So what you need to do is create an Bitmap[4] array with 1,2,3 bitmaps and then change this line to load them: Dim myIcon As Icon = Icon.FromHandle(myBitmap[e.RowIndex % 3].GetHicon())` is there any other solution just fill a different color only because my image file is Transparent png – roy Jun 28 '23 at 09:28
  • `graphics.FillEllipse(..)` a proper size rectangle at the center then `graphics.DrawImage(...)` your image at the center of the ellipse rectangle. What's the point from converting the resource image to Icon? Just draw the image using an overload that takes the source and destination rectangles. Also, you don't need to `Dim myBitmap As New Bitmap(My.Resources.money)`. The image you call is a new one already which means you should dispose of it when you done. Get it with the `Using` statement. `Using myBitmap = My.Resources.money ... End Using`. – dr.null Jun 28 '23 at 12:29
  • Set `graphics.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias` before filling the ellipse. – dr.null Jun 28 '23 at 12:33
  • @dr.null , thanks for your reply .`Using myBitmap = My.Resources.money Dim myIcon As Icon = Icon.FromHandle(myBitmap(e.RowIndex Mod 3).GetHicon()) End Using` but still error `class 'Bitmap' cannot be indexed because it has no default property.` – roy Jun 28 '23 at 12:39
  • `myBitmap` is not an array of images. It's a single image. Do as I said and fill the ellipses with different colors. Create an array of colors to pick one based on your coloring rule. Forget any `Icon` code and try. – dr.null Jun 28 '23 at 12:45
  • @dr.null ,`myBitmap is not an array of images It's a single image.` it is a single image `Create an array of colors to pick one based on your coloring rule.` please guide me to make the color of each row like the screenshot I attached – roy Jun 28 '23 at 12:56
  • Sure, hold on please. – dr.null Jun 28 '23 at 13:20
  • @dr.null , okay I'll wait for you – roy Jun 28 '23 at 13:39

2 Answers2

2

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

dr.null
  • 4,032
  • 3
  • 9
  • 12
  • thank you for your answer but why does the image form not fit and I have posted the results from you – roy Jun 28 '23 at 14:39
  • @mbmt Use your own sizes. It's just an example. For this to fit, set `DataGridView1.RowTemplate.Height = 48` and `DataGridView1.RowHeadersWidth = 48`. – dr.null Jun 28 '23 at 14:42
  • can the image be zoomed or autosized according to row header Height and width – roy Jun 28 '23 at 14:49
  • `Dim ellipseRect = Rectangle.Inflate(e.CellBounds, -3, -3) and Dim imgRect = Rectangle.Inflate(ellipseRect, -3, -3)` if I use the code then the result is like the one I posted just now – roy Jun 28 '23 at 15:02
  • the code you updated just now matches the rowtemplate height – roy Jun 28 '23 at 15:33
  • means I have to update some colors from this code `colors = {Color.Red, Color.Green, Color.Orange, Color.Black}` – roy Jun 28 '23 at 15:48
  • @mbmt Based on what? What is your coloring rule.? Read [this](https://stackoverflow.com/posts/comments/135011523) again. You can get the value of the first cell (Column1) and select the relevant color accordingly. Or dictionary as per Jimi's suggestion. You need to edit your question and to clarify what you need. – dr.null Jun 28 '23 at 16:06
  • Do you think it's better if I make a new question or revise the question in this post – roy Jun 28 '23 at 16:09
  • thanks for the answer from you and [link](https://stackoverflow.com/questions/76575084/how-to-add-different-colour-icons-images-automatic-to-the-row-header-of-a-datagr) this is the latest post from me, I hope you can help me – roy Jun 28 '23 at 16:30
  • I just updated the screenshot when resizing the datagridview row, the image size should remain the same size as before. Is it possible that the image can be set to be resizeable to false or is there a solution from you – roy Jun 29 '23 at 08:13
  • 1
    thank you, the addition of the size option went perfectly – roy Jun 29 '23 at 11:01
1

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
Jimi
  • 29,621
  • 8
  • 43
  • 61
  • thanks for the answer from you but the color of the icon is only one color and why is there a different color on the border in the row – roy Jun 28 '23 at 14:35
  • The color is just one because, as described in the notes, you haven't specified what logic changes the color. You can implement that logic yourself, since you know it, then just pass the color to the `DataGridViewIcon.DrawImage()` method – Jimi Jun 28 '23 at 15:03
  • @Jimi , is it possible that each color is based on the data from "column1" – roy Jun 28 '23 at 15:28
  • Of course. You could pre-build a `Dictionary(Of String, Color)` that matches the Value of the current Cell at `Columns(0)` with a Color value. If you want help with this matter, you have to express the logic that defines a Color selection explicitly and in details. Add this information to the question, if you cannot work this out yourself – Jimi Jun 28 '23 at 15:37
  • thanks for the answer from you and [link](https://stackoverflow.com/questions/76575084/how-to-add-different-colour-icons-images-automatic-to-the-row-header-of-a-datagr) this is the latest post from me, maybe you can help me – roy Jun 28 '23 at 17:05