vb.netwinformsdatagridviewrowheaderdatagridrowheader

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


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

Solution

  • 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