0

How to add different colour icons/images automatic to the Row Header of a DataGridView based on value in column1?

is it possible to pick up all the colors randomly without manually setting the color based on the value in "COLUMN1" and can also take the value in combination between "COLUMN1" and "COLUMN2"?

Thanks

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)
    Table1.Rows.Add("Item2", 60, 70)
    Table1.Rows.Add("Item3", 85, 85)
    Table1.Rows.Add("Item4", 77, 21)
    Table1.Rows.Add("Item2", 60, 70)

    DataGridView1.RowTemplate.Height = 48
    DataGridView1.RowHeadersWidth = 48
    DataGridView1.DataSource = Table1
End Sub
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
        Dim g = e.Graphics
        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)
        Dim colorIndex = e.RowIndex Mod colors.Length

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

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

        e.Handled = True
    End If
End Sub
dr.null
  • 4,032
  • 3
  • 9
  • 12
roy
  • 693
  • 2
  • 11
  • Hello! Still not clear what you are asking. Columns(0) of type string named `Column1` has the `Item1, 2, 3` values. Do you mean this column or the of type `Integer` ones (Columns(1) & (Columns(2))? Taking the first row for example, what is the color if it's `Item 1` or `44` or `99` or perhaps `44 > 99` or `44 < SomeOtherValue`...etc. What is the rule? Please elaborate. Thanks. – dr.null Jun 28 '23 at 17:13
  • @dr.null , sorry I'm late to reply. `Do you mean this column or the of type Integer ones (Columns(1) & (Columns(2))? Taking the first row for example, what is the color if it's Item 1 or 44 or 99 or perhaps 44 > 99 or 44 < SomeOtherValue...etc.`. what I mean is a combination with Column1 with Column2 being the key and also in the code, please provide a comment if I only want to use one column as the key. So if column 1 and column 2 appear 2 times then the color will be the same as it should be. – roy Jun 29 '23 at 08:08
  • @dr.null , For colors, it's up to any color at random, whether the color must be listed or used automatically from the color collection. – roy Jun 29 '23 at 08:08

1 Answers1

1

So, you mean using the same random color for the duplicate rows? For that, you need to group the rows by the cell values and use a color for each group. Doing this in the CellPainting event is a heavy task since it's being raised for each cell in the grid. Hence, I suggest adding a hidden DataColumn to the DataTable to keep the color index of each row. The indices are set when you bind the control the first time, and when the user modifies the values.

' +
Imports System.Reflection

Private colors As Color()
Private bmp As Bitmap

Sub New()
    InitializeComponent()

    ' To reduce the flickering...
    DataGridView1.GetType().
    GetProperty("DoubleBuffered",
                BindingFlags.Instance Or BindingFlags.NonPublic).
    SetValue(DataGridView1, True)
End Sub

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

    ' Collect dark colors...
    colors = GetType(Color).
        GetProperties(BindingFlags.Public Or BindingFlags.Static).
        Where(Function(pi) pi.PropertyType = GetType(Color)).
        Select(Function(pi) CType(pi.GetValue(GetType(Color), Nothing), Color)).
        Where(Function(c)
                    Return ((
                    c.R * 0.299F +
                    c.G * 0.587F +
                    c.B * 0.114F) / 256.0F) <= 0.5F
                End Function).ToArray()

    bmp = My.Resources.Money

    Dim Table1 = New DataTable("TableName")

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

    Table1.Rows.Add("Item1", 44, 99)
    Table1.Rows.Add("Item2", 50, 70)
    Table1.Rows.Add("Item3", 75, 85)
    Table1.Rows.Add("Item2", 60, 70)
    Table1.Rows.Add("Item3", 75, 85)
    Table1.Rows.Add("Item4", 77, 21)
    Table1.Rows.Add("Item2", 50, 70)
    ' ...etc.

    DataGridView1.DataSource = Table1
    DataGridView1.Columns("ColorIndex").Visible = False
    UpdateColorColumn()
    Table1.AcceptChanges()
End Sub

Protected Overrides Sub OnFormClosed(e As FormClosedEventArgs)
    MyBase.OnFormClosed(e)
    bmp.Dispose()
    DirectCast(DataGridView1.DataSource, IDisposable)?.Dispose()
End Sub

Private Sub DataGridView1_CellValueChanged(sender As Object,
                e As DataGridViewCellEventArgs) _
                Handles DataGridView1.CellValueChanged
    UpdateColorColumn()
End Sub

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

        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 drv = DirectCast(DataGridView1.Rows(e.RowIndex).DataBoundItem, DataRowView)
        Dim colorIndex = Convert.ToInt32(drv.Item("ColorIndex"))
        Dim g = e.Graphics
        Dim gs = g.Save()

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

        Using 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

Private Sub UpdateColorColumn()
    Dim dt = DirectCast(DataGridView1.DataSource, DataTable)
    dt.EndInit()
    Dim groups = dt.AsEnumerable().
        GroupBy(Function(g) New With {
        Key .Col1 = g.Item(0),
        Key .Col2 = g.Item(1),
        Key .Col3 = g.Item(2) ' Remove this to group by the first two.
    })

    For i = 0 To groups.Count - 1
        Dim group = groups(i)
        Dim colorIndex = i Mod colors.Length

        For Each row In group
            row("ColorIndex") = colorIndex
        Next
    Next
End Sub

If you have a DataBase, fill the DataTable before you add the ColorIndex column. To update the DB, you don't need to do anything special regarding the additional column(s). The INSERT and UPDATE commands execute their SQL CommandText queries and ignore anything else in the Columns collection. However, you can get a copy of the current DataTable and exclude the non-database columns.

For example:

Dim dt = Table1.DefaultView.
    ToTable(False, Table1.Columns.
    Cast(Of DataColumn).
    Where(Function(c) c.ColumnName <> "ColorIndex").
    Select(Function(c) c.ColumnName).ToArray())

As another option, use the DataGridViewRow.Tag property to keep the color index. The same could have been written like this.

Private colors As Color()
' ...

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

    ' ...

    DataGridView1.DataSource = Table1
    UpdateColorIndices()
End Sub

' ...

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

        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
        Dim imgRect = New Rectangle(
            ellipseRect.X + (ellipseRect.Width - imgSize) \ 2,
            ellipseRect.Y + (ellipseRect.Height - imgSize) \ 2,
            imgSize, imgSize)
        Dim colorIndex = Convert.ToInt32(DataGridView1.Rows(e.RowIndex).Tag)
        Dim g = e.Graphics
        Dim gs = g.Save()

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

        Using 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

Private Sub UpdateColorIndices()
    DataGridView1.EndEdit()
    Dim groups = DataGridView1.Rows.Cast(Of DataGridViewRow).
        Where(Function(r) r.Index <> DataGridView1.NewRowIndex).
        GroupBy(Function(r) New With {
        Key .Col0 = r.Cells(0).Value,
        Key .Col1 = r.Cells(1).Value,
        Key .Col2 = r.Cells(2).Value
    })

    For i = 0 To groups.Count - 1
        Dim index = i Mod colors.Length
        For Each row In groups(i)
            row.Tag = index
        Next
    Next
    DataGridView1.Invalidate()
End Sub

Also, you could create a Dictionay(Of Integer, Color) where each KeyValuePair holds the row index and the ellipse color.

Private colors As Color()
Private ReadOnly dictColors As New Dictionary(Of Integer, Color)
' ...

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

        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
        Dim imgRect = New Rectangle(
            ellipseRect.X + (ellipseRect.Width - imgSize) \ 2,
            ellipseRect.Y + (ellipseRect.Height - imgSize) \ 2,
            imgSize, imgSize)
        Dim g = e.Graphics
        Dim gs = g.Save()

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

        Using ellipseBrush = New SolidBrush(dictColors(e.RowIndex))
            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

Private Sub UpdateColorIndices()
    DataGridView1.EndEdit()
    dictColors.Clear()
    Dim groups = DataGridView1.Rows.Cast(Of DataGridViewRow).
        Where(Function(r) r.Index <> DataGridView1.NewRowIndex).
        GroupBy(Function(r) New With {
        Key .Col0 = r.Cells(0).Value,
        Key .Col1 = r.Cells(1).Value,
        Key .Col2 = r.Cells(2).Value
    })

    For i = 0 To groups.Count - 1
        Dim index = i Mod colors.Length
        For Each row In groups(i)
            dictColors(row.Index) = colors(index)
        Next
    Next
    DataGridView1.Invalidate()
End Sub
dr.null
  • 4,032
  • 3
  • 9
  • 12
  • Thank you for your answers. if i don't set column data in datatable because go directly from database to datagridview and because it directly autogenerate column in datagridview how can I resolve column "ColorIndex" – roy Jun 30 '23 at 14:49
  • thanks for the answer update for the second and third options went perfectly but your first option failed so I posted the code because you definitely know the problem – roy Jul 01 '23 at 03:23
  • sorry for my mistake ,So I made a new post for the question – roy Jul 01 '23 at 07:08
  • [link](https://stackoverflow.com/questions/76593652/how-to-add-different-colour-icons-images-automatic-to-the-row-header-of-a-datagr) I made a recent post I hope you can help. Once again I apologize for the mistake – roy Jul 01 '23 at 07:20
  • @mbmt Added some details here regarding the `.ToTable` part to not confuse you. This part wasn't explained as it should. Sorry for this one and good luck. – dr.null Jul 01 '23 at 14:07
  • yes it's okay because you have helped and provided the best guide – roy Jul 02 '23 at 04:09
  • 1
    I use option 3 which is dictionary but I have an error `Index was outside the bounds of the array.` . Record there are 83 rows in DataGridView – roy Jul 03 '23 at 13:06
  • @mbmt Just saw your last comment. Seems lost among the others. Yes, you're right. There's a bug where the `i` is used rather than `index`. Fixed. Good catch. – dr.null Jul 04 '23 at 13:06
  • Sorry I replied late, so the solution is what if I want to use the third dictionary option – roy Jul 05 '23 at 07:20
  • @mbmt Use it no problem. The bug was `dictColors(row.Index) = colors(i)` and fixed to `dictColors(row.Index) = colors(index)`. – dr.null Jul 05 '23 at 09:35
  • I have a recent post maybe you can help. https://stackoverflow.com/questions/76619066/why-datagridviewlinkcolum-doesnt-show-any-of-the-data-property-in-vb-net – roy Jul 05 '23 at 09:46
  • for option 2 `DataGridViewRow.Tag` Sorry to disturb your time, if I sort in the datagridview column then change the icon to one color. Is there a solution? – roy Jul 06 '23 at 08:45
  • for option 2 `DataGridViewRow.Tag` if I sort in the datagridview column then change the icon to one color. Is there a solution? – roy Jul 13 '23 at 04:48
  • @mbmt I don't understand what you mean. However, and as a guess, handle the [DataGridView.Sorted](https://learn.microsoft.com/en-us/dotnet/api/system.windows.forms.datagridview.sorted?view=windowsdesktop-7.0) event to call the `UpdateColorIndices` and examine the result. – dr.null Jul 13 '23 at 08:40
  • sorry if I'm late to reply you. Thank You . I have a recent post maybe you can help me . https://stackoverflow.com/questions/76714309/how-to-fill-in-datatable-from-selecting-checkbox-datagridview-in-form-2-to-datag?noredirect=1#comment135251945_76714309 – roy Jul 19 '23 at 11:13