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… i.e. 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
A snippet of my code:
Dim G As Graphics
Dim BBG As Graphics
Dim BB As Bitmap
Dim R As Rectangle
..................................................................
picMainScreen.Visible = True
G = picMainScreen.CreateGraphics
BB = New Bitmap(picMainScreen.Width, picMainScreen.Height)
For x = 0 To 256 * 3 - 1 Step 24
For y = 0 To 240 * 3 - 1 Step 24
R = New Rectangle(New Point(x, y), New Point(24, 24))
G.DrawRectangle(Pens.Black, R)
Next
Next
In this snippet of code, picMainScreen was a PictureBox that was originally not supposed to be visible.
Then through some conditions, picMainScreen was SUPPOSED to TURN Visible.
And THEN, the code draws all the rectangles onto the picture.
However, that isn't the case: the rectangles are first drawn onto the picture, and THEN the picture becomes visible.
Why does this happen? And what's the remedy?
Your rectangle instantiation is interesting since you are using two parameters of type Point.
R = New Rectangle(New Point(x, y), New Point(24, 24))
The Microsoft Docs show these parameter types;
Rectangle(Point, Size)
Initializes a new instance of the Rectangle class with the specified location and size.
https://learn.microsoft.com/en-us/dotnet/api/system.drawing.rectangle.-ctor?view=netframework-4.7.2
I'm not sure if the parameter types are the problem; however you are mixing drawing graphics with a picture control and they might not cohabitate in the same space well.
You could try calling DoEvents() after making the picture control visible to force it to be displayed first.
I have a parent window that has a panel on the left hand side into which some buttons are placed. There is also a splitter added, so that the user can adjust the size of this panel. When a user presses one of the buttons, a form should appear on the right hand size - defaulting to the width of the remaining area of the window. In order to work out how wide this child form needs to be, I have taken the client width and subtracted the width of the panel and the splitter from this, however it is always slightly too big. I can simply subtract an additional 4 from the calculation to get it to work - but this feels unstable to me, as I don't know where those 4 pixels have come form! How do I calculate this correctly. My Code is below.
Dim xPos As Integer = Me.Panel1.Width + Me.Splitter1.Width
Dim yPos As Integer = 0
Dim childFormWidth As Integer = Me.ClientSize.Width - xPos
Dim childFormHeight As Integer = 200
myChildForm.Show()
myChildForm.Location = New Point(xPos, yPos)
myChildForm.Size = New Size(ChildFormWidth, myHeight)
Thanks Paul.
Public Class Form1
Private Sub Timer1_Tick() Handles Timer1.Tick
Label1.Text = TimeOfDay
Label2.Text = System.DateTime.Now.ToString("MM/d/yyy")
Me.BackColor = ColorTranslator.FromHtml("#" & DateAndTime.Now.ToString("HHmmss"))
Label1.ForeColor = Color.White
Label2.ForeColor = Color.White
End Sub
End Class
In my code above, the background color changes to a hex color code depending on what the time is. However, I would like to change this because the colors that come with these codes are too dark.
Instead I am looking for the code to make the background color the % in red, green, and blue containers. (RGB colors)
For ex, if the time is 11:22:33, then I would like the background color to be 11% red, 22% green and 33% blue. Does this make sense? I am a beginner, and any help is much appreciated.
You need to interpolate between 0 and 255 and use the interpolated value as either red, green or blue value, instead of using the time component directly, if you want to use the full color range. This is because the color components are represented 1 Byte each.
You basically stretch your 0-24 hours to values of 0-255, where Hour=0 corresponds to Red=0 and Hour=24 corresponds to Red=255.
Dim R As Byte = CByte(Date.Now.Hour / 23 * 255)
This is quite similar to your "11% Red" approach.
The other parts would be defined similar,
Dim G As Byte = CByte(Date.Now.Minute / 59 * 255)
Dim B As Byte = CByte(Date.Now.Second / 59 * 255)
The linear interpolation formula in general is
New_Value = (Value - Min) / (Max - Min) * (New_Max - New_Min) + New_Min
Here Min and New_Min is 0, which simplifies the formula somewhat.
To actually assign the color you don't need to set it through construction of a HTML color. You simply can use the Color.FromArgb function. Just type it in in Visual Studio. For some reason IntelliSense (the feature that shows you what methods there are available while typing in the IDE) hides the function in some cases.
Me.BackColor = Color.FromArgb(R, G, B)
This is nothing different than your HTML approach, just simpler (the #321224 value is just a representation of three bytes in hexadecimal, in the form of #RRGGBB).
In the strictest sense the answer above is not exactly what you wanted to use. To use your percentage based approach you would construct the RGB values as
Dim R As Byte = CByte(Date.Now.Hour / 100 * 255)
Dim G As Byte = CByte(Date.Now.Minute / 100 * 255)
Dim B As Byte = CByte(Date.Now.Second / 100 * 255)
but this would not yield you the full color range as well (even 59% of 255 is only 150, so you would never see values between 150 and 255). Linear interpolation is the way to go.
My Goal is to create a maze in VS using VB.net, I currently have managed to make a random Generator that makes the "maze" and shows the location of the last wall made.
Horizontalwalls = Randomizer.Next(60, 91) 'Makes 60 - 90 Horizontal Walls
VirticalWalls = Randomizer.Next(60, 91) 'Makes 60 -90 Vertical Walls
Dim HLoops = 0 'counter for Horizontal walls
Dim VLoops = 0
lbxHorizontal.Items.Clear() 'empties the list box i have which stores the walls location
lbxvertical.Items.Clear()
Do While HLoops < (Horizontalwalls)
HLoops += 1 'adds to the counter
lbxHorizontal.Items.Insert(0, Randomizer.Next(0, 10))
lbxHorizontal.Items.Insert(0, Randomizer.Next(0, 10))
'Attempt at making visable walls
pbxhorizontalwall.Top = (lbxHorizontal.Items.Item(0) * GridSize - 2) 'This and next line puts the wall in desired location
pbxhorizontalwall.Left = (lbxHorizontal.Items.Item(1) * GridSize - 2)
Loop
however the only way i know to make all the walls visible is to make 90 horizontal wall pictures, go though naming them all, then GLaaa... there must be a easier way to copy the same image over the screen at the desired location.
At the moment, all i really want to know is the line of code that will copy the image (and maybe a way to mass clear them all when the maze is reset) and then i'll work out how to get it into place...
You first create the list of images with:
Dim imageList As New List(Of Bitmap)
imageList.Add("image to add") 'do it for all the images you have
Then create a bitmap:
Dim bitmapWall as Bitmap = New Bitmap(widthOfbitmap, heightofbitmap, Drawing.Imaging.PixelFormat.Format24bppRgb)
Draw the list of images to the bimap:
Dim objGraphics As Graphics = Graphics.FromImage(bitmapWall)
For i = 0 To imageList.Count
objGraphics.DrawImage(imageList(i), x, y, imageList(i).Width, imageList(i).Height)
Next
objGraphics.Dispose()
x,y is the coordinates of where your images are drawn (you should change them for every iteration)
Lastly:
Me.BackgroundImage = bitmapWall
Me.Invalidate()
Dont forget to dispose the list and the bitmap in the end.
valter