简体   繁体   中英

Colour Datagridview cells backcolor based on cell value (shaded like excel conditional formatting) vb.net

In a datagridview I want to colour the back colour of one of my columns, which is called "COST" with the highest value being RED, and the lowest value being GREEN. every other value between would need to be a shade of red to yellow to green.

I found one thing Color Interpolation Between 3 Colors in .NET but it was a bit to complex for me to understand, even when converted back to VB.net I didn't know what it is that I need to plug into the function, that's even if it would work in the way I want it to.

is there a simple class or function I write or add in that would take the max, the min, and shade all the values between. I don't mind if it has to loop through all rows.

my current data, has 345 rows, max value is 1673.86 and min value is 4.99 the data is from a data source and users can't change the cell values. I'm also working in VB.net

Well… even after setting this up, I still am not sure “how” this helps the user. However, it is not mine to question such things. I am confident there are numerous ways to do this and below is a hacky approach that may work for your needs.

In my approach below, I basically created a method that takes a cells value and returns a Color based on the requirements you described. In the example below, I made a method that loops though all the rows in the grid and sets the cells color using the previous method. You “could” put this code in one of the grid's cell or row “Draw” methods, however this seems like overkill since, as you commented… ”users can't change the cell values.” … Therefore, if the user can't change any cell values, then we would only need to call our method when the data is loaded or reloaded OR if the grid gets sorted or filtered.

Lastly, this solution is pretty specific to the posted requirements. In other words, depending on the colors used and the ranges of the data, this could easily get reduced to just the three colors without any variance. Also, you would almost assuredly need to alter the code if you changed the colors. In other words, this solution is “specific” with the color “Red” as the Max value, “Green” as then Min value and “Yellow” as the Mid value.

To start, an observation using the Red, Yellow and Green Colors. Below are the component colors for each of the colors used. The first parameter is the alpha value and the next three parameters are the Red, Green and Blue component values (0-255). So for Red, then Red component is set to max 255 and the Green and Blue values are set to min zero (0). If you look at the Red, Green, Blue components of these three colors you may notice something we may be able to use…

Color Red = Color.FromArgb(255, 255, 0, 0);  // Max
Color Yellow = Color.FromArgb(255, 255, 255, 0);  // Mid
Color Green = Color.FromArgb(255, 0, 255, 0);  // Min

If we look at the “difference” between the Red and Yellow components, then the only difference between them is the Green component… it is set to max 255 for Yellow. The same idea applies to the difference between Yellow and Green components… obviously, the Red component is set to zero (0). This convenient “difference” of the colors may be useful.

If we knew what the Max, Min and Mid values of all the data (which we will get), then we would know that if the value of the cell was less than Max BUT GREATER THAN Mid, then we would know that the color would belong in the range from Yellow to Red and we only need to adjust the Green component. If the cells value is LESS THAN Mid, then we would know that the cells color is in the Green to Yellow range and we only need to adjust the Red component. Let me clarify with a simple example.

Given the colors above we want to know how many “different” possible values are between all the colors. This would be 255 between Green to Yellow and 255 between Yellow and Red. So there are only 255x2 = 510 different possible colors. Given ALL the data values, we will never be able to display more than 510 different colors. It should be noted, that these “different” colors are certainly noticeable when looking at the numbers, however, this includes many numbers that differ so little that the user would NEVER notice the subtle “color” difference.

Continuing…

Let's say that the highest value (Max) in the data is 2000, and the lowest value (Min) is 0. This would make Mid = 1000. This gives us the two ranges… ie 2000-1000 is the range from Red to Yellow. And 1000-0 is the range from Yellow to Green.

Next, we need to crudely calculate what I am calling a Steps (partition) in the code below. We will get this number by dividing the total of all the data (0-2000) by the number of color values we have… (510)…

2000 / 510 = 3.921

If we round this number up to 4, what does this mean? The main thing this tells us is that given all the data, IF two numbers differ by LESS THAN 4, THEN those two “different” values will map to the “same” color. This may be a problem if all the data differs in only small amounts and the range is wide. In our example here, we will increment the proper color component by 1 for every 4 values in the cell. Example, the cells value is 500, so that is in the Green to Yellow range so we would set the cells Red component to 500 / 4 = 125. This would be the Red components value for the cell.

You can note that if the range of data is large, like, 0-10,000, then Steps may/would “increase” in size and may be large. Meaning that two numbers that differ by a large amount may map to the same color. Just a heads up.

The code below creates four (4) global decimal values ( Max , Min , Mid and Steps ) as they will not change unless the data changes.

Dim GridTable As DataTable
Dim Min As Decimal = Decimal.MaxValue
Dim Max As Decimal = Decimal.MinValue
Dim Mid As Decimal = Decimal.MinValue
Dim Steps As Decimal = Decimal.MinValue

Once the GridTable is filled with data, we will call the method below that loops through the table and sets our global variables above.

Private Sub SetMinMax()
  Max = Decimal.MinValue
  Min = Decimal.MaxValue
  Dim temp As Decimal
  For Each row As DataRow In GridTable.Rows
    temp = CDec(row(1))
    If temp > Max Then
      Max = temp
    End If
    If temp < Min Then
      Min = temp
    End If
  Next
  
  If ((Not (Max = Decimal.MinValue)) And (Not (Min = Decimal.MaxValue))) Then
    Dim total As Decimal = (Max - Min)
    Mid = total / 2
    Steps = total / 510
    If (Steps Mod 2 = 0) Then
      Steps = Convert.ToInt32(Steps) + 1
    End If
    Debug.WriteLine("Max: " + Max.ToString())
    Debug.WriteLine("Min: " + Min.ToString())
    Debug.WriteLine("Mid: " + Mid.ToString())
    Debug.WriteLine("Total values: " + total.ToString())
    Debug.WriteLine("Step: " + Steps.ToString())
  End If
End Sub

Next our method to return the proper Color given a cells value. The first three if statements look for the Max, Mid and Min values as we know what those colors are. If the code continues then we check to see if the number belongs to the Red-Yellow group or the Yellow-Green group.

If the target number is Less than Mid, then we know we have a number in the Green to Yellow range and we need to find the Red component of the color. We get this number from the difference between the Min and the target number. Then we divide that number by the Step value. A final check for staying in bounds then we have the Red component for this target value and return it's color.

If the target value is Less than Max, then we know we are in the Yellow to Red group and need to adjust the Green component of the color. Using the same strategy as above, we find the offset value by taking the difference between Max and the target value. Then divide by the Step value and return the color.

Private Function GetColorFromValue(targetValue As Decimal) As Color
  If (targetValue = Max) Then
    Return Color.FromArgb(255, 255, 0, 0)
  End If
  If (targetValue = Mid) Then
    Return Color.FromArgb(255, 255, 255, 0)
  End If
  If (targetValue = Min) Then
    Return Color.FromArgb(255, 0, 255, 0)
  End If
  Dim offsetValue As Decimal
  Dim offsetSteps As Decimal
  Dim rgbValue As Int32
  If (targetValue < Mid) Then
    offsetValue = targetValue - Min
    offsetSteps = offsetValue / Steps
    rgbValue = Convert.ToInt32(offsetSteps)
    If (rgbValue > 255) Then
      rgbValue = 255
    End If
    Return Color.FromArgb(255, rgbValue, 255, 0)
  End If
  If (targetValue < Max) Then
    offsetValue = Max - targetValue
    offsetSteps = offsetValue / Steps
    rgbValue = Convert.ToInt32(offsetSteps)
    If (rgbValue > 255) Then
      rgbValue = 255
    End If
    Return Color.FromArgb(255, 255, rgbValue, 0)
  End If
  Return Color.White
End Function

Next for usage, a method that loops through the grid rows and colors the cells using the above method…

Private Sub ColorCells()
  Dim dr As DataRowView
  Dim cellValue As Decimal
  For Each row As DataGridViewRow In DataGridView1.Rows
    If Not (row.IsNewRow) Then
      dr = CType(row.DataBoundItem, DataRowView)
      cellValue = CType(dr(1), Decimal)
      Dim cellColor As Color = GetColorFromValue(cellValue)
      row.Cells(1).Style.BackColor = cellColor
    End If
  Next
End Sub

To test, a complete example is below. Create a new VB winforms solution and drop a DataGridView onto the grid and the code below should look something like below.

在此处输入图像描述

A note, in the tests, I made a wide range to show that indeed the coloring is subtle and can be seen above. You may want to comment out the loop of random test data to test a known set of values. And finally, since the user can click on a column header to sort the grid, we will need to re-call our method each time it is sorted.

Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
  GridTable = GetData()
  SetMinMax()
  DataGridView1.DataSource = GridTable
  ColorCells()
End Sub


Private Function GetData() As DataTable
  Dim rand As Random = New Random()
  Dim dt As DataTable = New DataTable()
  dt.Columns.Add("Col0", GetType(String))
  dt.Columns.Add("DataToColor", GetType(Decimal))
  dt.Columns.Add("Random", GetType(Int32))
  dt.Rows.Add("C0R0", 2000.0, rand.Next(100))
  dt.Rows.Add("C0R1", 1750.02, rand.Next(100))
  dt.Rows.Add("C0R2", 1500.45, rand.Next(100))
  dt.Rows.Add("C0R3", 1250.0, rand.Next(100))
  dt.Rows.Add("C0R4", 1000.99, rand.Next(100))
  dt.Rows.Add("C0R5", 750.23, rand.Next(100))
  dt.Rows.Add("C0R6", 500.45, rand.Next(100))
  dt.Rows.Add("C0R7", 250.55, rand.Next(100))
  dt.Rows.Add("C0R8", 5.0, rand.Next(100))
  Dim randDecimal As Decimal
  For index = 0 To 349
    randDecimal = GetRandomDecimal(rand, 9999, 99)
    dt.Rows.Add("C0R" + (index + 9).ToString(), randDecimal, rand.Next(100))
  Next
  Return dt
End Function

Public Function GetRandomDecimal(rand As Random, wholeLen As Int32, decLen As Int32) As Decimal
  Dim whole1 As Int32 = rand.Next(1, wholeLen)
  Dim dec1 As Int32 = rand.Next(0, decLen)
  Dim value As Decimal
  Decimal.TryParse(whole1.ToString() + "." + dec1.ToString(), value)
  Return value
End Function


Private Sub DataGridView1_ColumnHeaderMouseClick(sender As Object, e As DataGridViewCellMouseEventArgs) Handles DataGridView1.ColumnHeaderMouseClick
  ColorCells()
End Sub

I hope this makes sense.

You want to leverage the DataGridView's DataBindingComplete event.

''' <summary>
''' Formats cell styles based on the data in certain cells.
''' </summary>
Private Sub dgvResults_DataBindingComplete(ByVal sender As Object, ByVal e As DataGridViewBindingCompleteEventArgs) Handles dgvResults.DataBindingComplete
    
    
    For Each r As DataGridViewRow In dgvResults.Rows

        ' Your code goes here to analyze the value of the cell(s).
        ' This is example code:
        If r.Cells(1).Value = "Yes" Then

            Dim style As New DataGridViewCellStyle
            style.BackColor = Color.Red
            style.ForeColor = Color.White
            style.SelectionBackColor = Color.Red
            style.SelectionForeColor = Color.White

            r.Cells(1).Style = style
            
        End If
        
    Next

End Sub

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM