Changing color in Datagrid Cell - My solution

  • Thread starter Thread starter Zanna
  • Start date Start date
Z

Zanna

Hi!

I hope jasmine can read this because she had my identical problem.

I solved it in this way and it seems to work fine, even if the re-painting
is "not-so-fast" :)

What do you need
- Obtain the VScrollBar for calculating the effective DataGrid client area.
- Trap the Scroll event: datagrid does not provide it, but the VScrollBar
do.
- Define a list of color/rows to color
- Do a row-painting each time
- the DataGrid scrolls
- the DataGrid paints (i.e.: the column size is changed by the user)
- a new back color is defined

Here is my code (sorry it is a little long).
I done a class that "extends" the DataGrid
In this code I havn't defined a ForeColor for cells, but as you could see is
really simple to improve it, maybe as a couple of Back-and-Fore colors
value.
Also this show how to color a full row: again is really simple to change it
for let it paint a single cell.

Every suggestion on this code is welcome.

-------- You can cut and paste in a new .vb file -------

Imports System.Data
Imports System

' Needed by DatagridExtender
Imports System.Windows.Forms
Imports System.Reflection
Imports System.Drawing


Public Class Form1
Inherits System.Windows.Forms.Form

#Region " Codice generato da Progettazione Windows Form "

Public Sub New()
MyBase.New()
InitializeComponent()
End Sub

Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean)
MyBase.Dispose(disposing)
End Sub

Friend WithEvents DataGrid1 As System.Windows.Forms.DataGrid

Private Sub InitializeComponent()
Me.DataGrid1 = New System.Windows.Forms.DataGrid
'
'DataGrid1
'
Me.DataGrid1.Location = New System.Drawing.Point(16, 24)
Me.DataGrid1.Size = New System.Drawing.Size(208, 144)
Me.DataGrid1.Text = "DataGrid1"
'
'Form1
'
Me.Controls.Add(Me.DataGrid1)
Me.Text = "Form1"

End Sub

#End Region

Dim dgx As DataGridExtender


Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles MyBase.Load
Dim t As New DataTable
dgx = New DataGridExtender(DataGrid1)
t.Columns.Add("Column1")
t.Columns.Add("Column2")
For i As Int32 = 1 To 20
t.Rows.Add(New Object() {"This is the long line #" +
i.ToString})

If i Mod 3 = 0 Then
dgx.SetRowBackColor(i, Drawing.Color.Red)
End If

Next i
DataGrid1.DataSource = t
End Sub
End Class



Public Class DataGridExtender

Private blnCEditActive As Boolean
Private objCTextBox As TextBox
Private WithEvents objCGrid As DataGrid
Private WithEvents objCVScrollBar As VScrollBar
Private objCRowBackColors As New System.Collections.Hashtable

Public Sub New(ByVal grid As DataGrid)
objCGrid = grid
objCVScrollBar = DirectCast(grid.GetType().GetField("m_sbVert",
_

BindingFlags.NonPublic Or _

BindingFlags.GetField Or _

BindingFlags.Instance).GetValue(grid), VScrollBar)

'hsb = (HScrollBar)grid.GetType().GetField("m_sbHorz",
BindingFlags.NonPublic|BindingFlags.GetField|BindingFlags.Instance).GetValue
(grid);
End Sub


Public Sub SetRowBackColor(ByVal rowIndex As Int32, ByVal
rowBackColor As Drawing.Color)
If objCRowBackColors.Contains(rowIndex) Then
objCRowBackColors.Remove(rowIndex)
End If
If (rowBackColor.ToArgb <> Drawing.SystemColors.Window.ToArgb)
Then
objCRowBackColors.Add(rowIndex, rowBackColor)
PaintRows()
End If
End Sub

Private Sub PaintRows()
Dim dt As DataTable = DirectCast(objCGrid.DataSource, DataTable)
If (Not objCGrid.DataSource Is Nothing) AndAlso _
(dt.Rows.Count > 0) AndAlso _
(objCRowBackColors.Count > 0) Then
Dim intLLeft As Int32 = objCGrid.GetCellBounds(0, 0).Left
Dim ht As DataGrid.HitTestInfo = objCGrid.HitTest(1,
intLLeft)
Dim cw As Int32
Dim maxw As Int32 = objCGrid.ClientSize.Width - intLLeft -
objCVScrollBar.Width
For i As Int32 = ht.Row To ht.Row + objCGrid.VisibleRowCount
If objCRowBackColors.Contains(i) Then
Dim g As Drawing.Graphics = objCGrid.CreateGraphics
Dim r As Rectangle
cw = 0
For col As Int32 = 0 To dt.Columns.Count - 1
r = objCGrid.GetCellBounds(i, col)

' this is for avoid painting over the
' scrollbox in the bottom-right
cw += r.Width
If cw > maxw Then
r.Width -= (cw - maxw)
End If
g.FillRectangle(New
SolidBrush(DirectCast(objCRowBackColors.Item(i), _
Color)), r)

g.DrawString(dt.Rows(i).Item(col).ToString, _
objCGrid.Font, _
New SolidBrush(objCGrid.ForeColor),
_
New RectangleF(r.X + 2, r.Y + 2,
r.Width, r.Height))
Next
End If
Next
End If
End Sub



Private Sub objCVScrollBar_ValueChanged(ByVal sender As Object,
ByVal e As System.EventArgs) Handles objCVScrollBar.ValueChanged
PaintRows()
End Sub

Private Sub objCGrid_Paint(ByVal sender As Object, ByVal e As
System.Windows.Forms.PaintEventArgs) Handles objCGrid.Paint
PaintRows()
End Sub

End Class
 
Public Class DataGridExtender

Private blnCEditActive As Boolean
Private objCTextBox As TextBox

Ok, the TextBox does nothing :)

It was a test-object: in the same way you paint a cell you can overlaps
objects adding them to the DataGrid.Controls and inflating the Rectangle by
1pixel for side (Rectange.Inflate(1,1)).

The rectangle have to be inflated *before* to assigning it to
Control.Bounds.

That's all :)
 
Thank You SOOO Much Zanna!!!!
Your code works very well and it was very easy to
understand!!!!

-----Original Message-----
Hi!

I hope jasmine can read this because she had my identical problem.

I solved it in this way and it seems to work fine, even if the re-painting
is "not-so-fast" :)

What do you need
- Obtain the VScrollBar for calculating the effective DataGrid client area.
- Trap the Scroll event: datagrid does not provide it, but the VScrollBar
do.
- Define a list of color/rows to color
- Do a row-painting each time
- the DataGrid scrolls
- the DataGrid paints (i.e.: the column size is changed by the user)
- a new back color is defined

Here is my code (sorry it is a little long).
I done a class that "extends" the DataGrid
In this code I havn't defined a ForeColor for cells, but as you could see is
really simple to improve it, maybe as a couple of Back- and-Fore colors
value.
Also this show how to color a full row: again is really simple to change it
for let it paint a single cell.

Every suggestion on this code is welcome.

-------- You can cut and paste in a new .vb file -------

Imports System.Data
Imports System

' Needed by DatagridExtender
Imports System.Windows.Forms
Imports System.Reflection
Imports System.Drawing


Public Class Form1
Inherits System.Windows.Forms.Form

#Region " Codice generato da Progettazione Windows Form "

Public Sub New()
MyBase.New()
InitializeComponent()
End Sub

Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean)
MyBase.Dispose(disposing)
End Sub

Friend WithEvents DataGrid1 As System.Windows.Forms.DataGrid

Private Sub InitializeComponent()
Me.DataGrid1 = New System.Windows.Forms.DataGrid
'
'DataGrid1
'
Me.DataGrid1.Location = New System.Drawing.Point (16, 24)
Me.DataGrid1.Size = New System.Drawing.Size(208, 144)
Me.DataGrid1.Text = "DataGrid1"
'
'Form1
'
Me.Controls.Add(Me.DataGrid1)
Me.Text = "Form1"

End Sub

#End Region

Dim dgx As DataGridExtender


Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles MyBase.Load
Dim t As New DataTable
dgx = New DataGridExtender(DataGrid1)
t.Columns.Add("Column1")
t.Columns.Add("Column2")
For i As Int32 = 1 To 20
t.Rows.Add(New Object() {"This is the long line #" +
i.ToString})

If i Mod 3 = 0 Then
dgx.SetRowBackColor(i, Drawing.Color.Red)
End If

Next i
DataGrid1.DataSource = t
End Sub
End Class



Public Class DataGridExtender

Private blnCEditActive As Boolean
Private objCTextBox As TextBox
Private WithEvents objCGrid As DataGrid
Private WithEvents objCVScrollBar As VScrollBar
Private objCRowBackColors As New System.Collections.Hashtable

Public Sub New(ByVal grid As DataGrid)
objCGrid = grid
objCVScrollBar = DirectCast(grid.GetType ().GetField("m_sbVert",
_

BindingFlags.NonPublic Or _

BindingFlags.GetField Or _

BindingFlags.Instance).GetValue(grid), VScrollBar)

'hsb = (HScrollBar)grid.GetType().GetField ("m_sbHorz",
Instance).GetValue
(grid);
End Sub


Public Sub SetRowBackColor(ByVal rowIndex As Int32, ByVal
rowBackColor As Drawing.Color)
If objCRowBackColors.Contains(rowIndex) Then
objCRowBackColors.Remove(rowIndex)
End If
If (rowBackColor.ToArgb <> Drawing.SystemColors.Window.ToArgb)
rowBackColor)
PaintRows()
End If
End Sub

Private Sub PaintRows()
Dim dt As DataTable = DirectCast
(objCGrid.DataSource, DataTable)
If (Not objCGrid.DataSource Is Nothing) AndAlso _
(dt.Rows.Count > 0) AndAlso _
(objCRowBackColors.Count > 0) Then
Dim intLLeft As Int32 =
objCGrid.GetCellBounds(0, 0).Left
Dim ht As DataGrid.HitTestInfo = objCGrid.HitTest(1,
intLLeft)
Dim cw As Int32
Dim maxw As Int32 =
objCGrid.ClientSize.Width - intLLeft -
 
Jasmine said:
Thank You SOOO Much Zanna!!!!
Your code works very well and it was very easy to
understand!!!!

Happy to be of some help

After this post I changed the code a little because it has a bug when
you scroll the grid horizontally.

You need yo trap also the HScrollBar_ValueChanged and the
DataGrid_CurrentCellChanged.

Also you need to change the PaintRows routine as this:

You will see I changed the RowColor to CellFormat that is simply

Private Structure CellFormat
Public Back As Color
Public Fore As Color
Public Sub New(ByVal backColor As Color, ByVal foreColor As
Color)
Back = backColor
Fore = foreColor
End Sub
End Structure


You'll need to change it into the SetRowBackColor sub (maybe you'll want
to rename it to SetRowColors)


Private Sub PaintRows()
If (Not MyBase.DataTable Is Nothing) AndAlso _
(objCRowColors.Count > 0) AndAlso _
(MyBase.DataTable.Rows.Count > 0) Then

Dim intLFirstRowIdx As Int32 = Me.FirstVisibleRow
Dim intLFirstColIdx As Int32 = Me.FirstVisibleColumn

Dim cw As Int32
Dim cf As CellFormat
Dim clip_rect As Rectangle

clip_rect = New Rectangle(intCRowHeaderWidth + 2, _
1, _
objCGrid.ClientSize.Width -
intCRowHeaderWidth - objCVScrollBar.Width - 2, _
objCGrid.ClientSize.Height)

Dim g As Drawing.Graphics = objCGrid.CreateGraphics
g.Clip = New Region(clip_rect)

For i As Int32 = intLFirstRowIdx To intLFirstRowIdx +
objCGrid.VisibleRowCount - 1
If objCRowColors.Contains(i) Then

Dim render_rect As Rectangle

For col As Int32 = intLFirstColIdx To
intLFirstColIdx + objCGrid.VisibleColumnCount - 1
render_rect = objCGrid.GetCellBounds(i,
col)

cf = DirectCast(objCRowColors.Item(i),
CellFormat)

g.FillRectangle(New
SolidBrush(cf.Back), render_rect)


g.DrawString(MyBase.DataTable.Rows(i).Item(col).ToString, _
objCGrid.Font, _
New SolidBrush(cf.Fore), _
New
RectangleF(render_rect.X + 2, _

render_rect.Y + 2, _

render_rect.Width - 4, _

render_rect.Height - 4))

Next
End If
Next
End If
End Sub

Public ReadOnly Property FirstVisibleRow() As Int32
Get
Return
DirectCast(objCGrid.GetType.GetField("m_irowVisibleFirst", _

Reflection.BindingFlags.NonPublic Or _
Reflection.BindingFlags.GetField
Or _

Reflection.BindingFlags.Instance)
).GetValue(objCGrid), _
Int32)
End Get
End Property

Public ReadOnly Property FirstVisibleColumn() As Int32
Get
Return objCGrid.FirstVisibleColumn
End Get
End Property
 
Zanna said:
After this post I changed the code a little because it has a bug when
you scroll the grid horizontally.

Oh, the intCRowHeaderWidth is (I think) always 20, but you can get it
via reflection asking for "m_kcxDefaultRowHdrWidth" that is a
BindingFlags.Static and not BindingBlag.Instance.

See youuu :)
 
You're my lifesaver, Zanna!!
I've noticed about the horizontal scrollbar and was trying
to figure it out myself. And then you post the new code
just right on time!! Thank you for your rapid response!!!
THANK YOU THANK YOU THANK YOU!!!
 
Hi Zanna,

Sorry It's me Jasmine again!
After testing out your code, there's an error that I
couldn't fix. My code always stopped at this line for some
reason:
cf = DirectCast(objCRowColors.Item(i), CellFormat)

The problem might be the CellFormat structure. I have
never used a structure before. Where should I place the
strucure??

Once again, Thank you so much!!

Jasmine
 
Jasmine said:
Hi Zanna,

Sorry It's me Jasmine again!
After testing out your code, there's an error that I
couldn't fix. My code always stopped at this line for some
reason:
cf = DirectCast(objCRowColors.Item(i), CellFormat)

The problem might be the CellFormat structure.

Yes, this is a structure where I store fore/back colors:

I changed its name to a more understandable

Private Structure CellColorInfo
Public Back As Color
Public Fore As Color
Public Sub New(ByVal backColor As Color, ByVal foreColor As
Color)
Back = backColor
Fore = foreColor
End Sub
End Structure

Also, for the previous "m_kcxDefaultRowHdrWidth" tips, if you use it be
sure you use "v_cxDefaultRowHdrWidth" for ARM processors (or do a try
catch on v_cxDefaultRowHdrWidth and in the catch section retry with
m_kcxDefaultRowHdrWidth)

Bye
 
Thanks Zanna!!! I got everything figured it out!!
-----Original Message-----


Yes, this is a structure where I store fore/back colors:

I changed its name to a more understandable

Private Structure CellColorInfo
Public Back As Color
Public Fore As Color
Public Sub New(ByVal backColor As Color, ByVal foreColor As
Color)
Back = backColor
Fore = foreColor
End Sub
End Structure

Also, for the previous "m_kcxDefaultRowHdrWidth" tips, if you use it be
sure you use "v_cxDefaultRowHdrWidth" for ARM processors (or do a try
catch on v_cxDefaultRowHdrWidth and in the catch section retry with
m_kcxDefaultRowHdrWidth)

Bye

.
 
Can anybody help to change the example code to allow the background colour of individual cells to be painted. I have got the above code working, but it only paints an entire row and I don't know how to change it to paint individual cells in the datagrid

Thanks
 
Back
Top