Have CustomListView resize last column to fit width - vb.net

I have a custom list view control in WinForms that applies some custom styles. This works well, however I have a part of the control that is not covered when the user maximises the screen (columns are set to fixed width) and ColumnHeaderAutoResizeStyle.None is set. I'd like to have the last column auto fill the gap.
To achieve this inside of the custom control I have added the following code to ListView.SizeChanged event.
Private Sub CustomListView_Resized(sender As Object, e As EventArgs) Handles Me.SizeChanged
Try
If (Not _isResizing) Then
_isResizing = True
Dim myListView As customListView = DirectCast(sender, customListView)
' Work out current column widths
Dim totalColumWidthInPx As Integer = 0
For i = 1 To Columns.Count - 1
totalColumWidthInPx += Me.Columns(i).Width
Next
' Increment the final column by the difference in width to make up the gap
Me.Columns.Item(Columns.Count - 1).Width += (myListView.ClientRectangle.Width - totalColumWidthInPx)
End If
Finally
_isResizing = False
End Try
End Sub
This is always giving me inconsistent results, the column width is incremented to much and adds the scrollbars as below.
Little bit of debug info.
Control: lvAppointments, Width (px): 701, Client Rectangle Width (px):
697, All Column Widths: 648, Diff: 49, Last Column: 234, Last Column +
Diff: 283

Related

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… 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

Programatically positioning a control in a VB.net user control

I have a user control in VB.Net VS2019. It is used to display a description, a value and units. It generally works if the description is not too large.
The controls in the user control are all labels.
In the resize event it sizes the descriptions width to 66% of the overall width, 22% for the value, and whatever is left over for the units.
Then it set the description's left to 0, the value's left to the width of the description (plus a little). For the unit's position, it adds the left position of the value plus its width and a little.
But when the program runs the controls overlap each other and do not take the entire space of the UserControl.
For example the UserControl is 236 wide, but it looks like everything is squished to about 1/2 or 2/3s.
I have set Anchor to none and docking to none.
I perform these calculations in the 'Resize' event as shown below. The code was ported from an old VB6 program.
Is the 'Auto' size property taking precedence over the width I specify?
Private Sub UserControl1_Resize(ByVal eventSender As System.Object, ByVal eventArgs As System.EventArgs) Handles MyBase.Resize
Dim lblUnits_Size As Integer
Label1.Height = MyBase.Height
Label2.Height = MyBase.Height
lblUnits.Height = MyBase.Height
Label1.Width = CInt(MyBase.Width * 0.6167)
Label2.Width = CInt(MyBase.Width * 0.22)
' I want the width of the units to be whatever is left over
lblUnits_Size = MyBase.Width - (Label1.Width + Label2.Width) - 6
If lblUnits_Size <= 0 Then lblUnits_Size = 1
lblUnits.Width = lblUnits_Size ' was -> TwipsToPixelsX(lbUnits_Size)
Label1.Left = 0
Label2.Left = (Label1.Left + Label1.Width) + 3
lblUnits.Left = (Label2.Left + Label2.Width) + 3
End Sub
I found it, the resize code needs to be called after the description changes. I guess ole VB6 did this automatically or someway the resize's were called after.

How can I dynamically change record textbox hieghts in my report to the same as the highest for each record?

I have a report with two fields (txtQuestion & txtLookingFor) which is populated via a query. Sometimes txtQuestion is much longer than txtLookingFor (or vice versa) and I want the printed report to appear even, by making the shorter of the two equal in height to the longer of the two - for each record on this report.
I have some VBA which is triggered by even "Detail OnFormat" which is supposed to retrive the "height" values of each of the text boxes, find the maximum, and set both heights to this value. I cannot seem to get (read) the height values (from each record). But I can manually set the height values (per record) to some arbitrary value via VBA if I want.
I have tried all the "Can Grow" and "Can Shrink" options. My text boxes do grow to their own height (per record), but I cannot seem to read what that value is, for use in my VBA code.
Private Sub Detail_Format(Cancel As Integer, FormatCount As Integer)
maxheight = 100
If (Me.txtQuestion.Height > maxheight) Then
maxheight = Me.txtQuestion.Height
End If
If (Me.txtLookingFor.Height > maxheight) Then
maxheight = Me.txtLookingFor.Height
End If
Me.txtQuestion.Height = maxheight
Me.txtLookingFor.Height = maxheight
Me.txtNotes.Height = maxheight
End Sub
Here's some code I found on tek-tips that gives a general idea of how to make textboxes look the same height by drawing rectangles around them. The controls themselves are not resizable.
Private Sub Detail_Print(Cancel As Integer, PrintCount As Integer)
Dim intMaxHeight As Integer
Dim ctl As Control
'Find highest control in Detail section that has a tag property of "Border"
For Each ctl In Me.Section(0).Controls
If ctl.Tag = "Border" Then
If ctl.Height > intMaxHeight Then
intMaxHeight = ctl.Height
End If
End If
Next
'Draw a box around each control in Detail that has a tag property of "Border"
For Each ctl In Me.Section(0).Controls
If ctl.Tag = "Border" Then
Me.Line (ctl.Left, ctl.Top) - Step(ctl.Width, intMaxHeight), vbBlack, B
End If
Next
End Sub
Reference:
https://www.tek-tips.com/viewthread.cfm?qid=1676341

Tablelayout panel rowstyle

I have a table layout panel that I am dynamically adding rows to using the following code:
attemptstlp.RowCount += 2
attemptstlp.Height = attemptstlp.Height + 62
attemptstlp.RowStyles.Add(New RowStyle(SizeType.Absolute, 30))
(just so you know attemptstlp is the name of the panel)
I am using a loop to process through these rows adding them. I am finding that all is working except half way through the row style stops applying (so if i want to add 24 lots of 2 rows the height will stop applying after the 12th lot of rows has been added).
Could anyone offer suggestions on why the rows are reverting (i assume) to auto size after half of them have been added. The only other lines of code that refer to this panel is the lines adding the text boxes and the lines to suspend and resume layout to help reduce the flickering and time taken to load.
The table layout panel has an inital height of 40 with 1 row of height 39 when first created.
Thanks in advance,
mrtechguy
This works perfectly for adding rows and controls in a TableLayoutPanel. Try and see.
'Define a blank Tablelayoutpanel with 3 columns in the design page
Dim TableLayoutPanel3 As New TableLayoutPanel()
TableLayoutPanel3.Name = "TableLayoutPanel3"
TableLayoutPanel3.Location = New System.Drawing.Point(32, 287)
TableLayoutPanel3.AutoSize = True
TableLayoutPanel3.Size = New System.Drawing.Size(620, 20)
TableLayoutPanel3.ColumnCount = 3
TableLayoutPanel3.CellBorderStyle = TableLayoutPanelCellBorderStyle.Single
TableLayoutPanel3.BackColor = System.Drawing.Color.Transparent
TableLayoutPanel3.ColumnStyles.Add(New ColumnStyle(SizeType.Percent, 26.34146!))
TableLayoutPanel3.ColumnStyles.Add(New ColumnStyle(SizeType.Percent, 73.65854!))
TableLayoutPanel3.ColumnStyles.Add(New ColumnStyle(SizeType.Absolute, 85.0!))
Controls.Add(TableLayoutPanel3)
'Create a button btnAddRow to add rows on each click
Private Sub btnAddRow_Click(sender As System.Object, e As System.EventArgs) Handles btnAddRow.Click
TableLayoutPanel3.GrowStyle = TableLayoutPanelGrowStyle.AddRows
TableLayoutPanel3.RowStyles.Add(New RowStyle(SizeType.Absolute, 20))
TableLayoutPanel3.SuspendLayout()
TableLayoutPanel3.RowCount += 1
Dim tb1 As New TextBox()
Dim tb2 As New TextBox()
Dim tb3 As New TextBox()
TableLayoutPanel3.Controls.Add(tb1 , 0, TableLayoutPanel3.RowCount - 1)
TableLayoutPanel3.Controls.Add(tb2, 1, TableLayoutPanel3.RowCount - 1)
TableLayoutPanel3.Controls.Add(tb3, 2, TableLayoutPanel3.RowCount - 1)
TableLayoutPanel3.ResumeLayout()
tb1.Focus()
End Sub

vb.net textbox / richtextbox GetPreferredSize not working

I have a winforms RichTextBox and TextBox (trying both). As I type text, I want the box to get bigger vertically (or smaller vertically) so that all the text is viewable.
I am using the following code in the RichTextBox TextChanged event:
RTB.Height = RTB.GetPreferredSize(New Size(RTB.Width, 0)).Height
This code works in most situations apart from one - when you put in a single word (without spaces) which is larger than the width of the box. Any ideas?
Thanks.
Found the following answer already on Stackoverflow! Just had to search better ...
Private Sub rtb_ContentsResized(ByVal sender As Object, ByVal e As System.Windows.Forms.ContentsResizedEventArgs) Handles txtQuestion.ContentsResized
Dim h = e.NewRectangle.Height, w = e.NewRectangle.Width
h = Math.Max(h, sender.Font.Height)
h = Math.Min(h, Me.ClientSize.Height - 10 - sender.Top)
h += sender.Height - sender.ClientSize.Height + 1
sender.Height = h
End Sub
from
Measure String inside RichTextBox Control
Try experimenting with the RTB min and max size properties.
Sounds like setting a max width may address your issue.