vb.net - Sort datagridview column numerically - vb.net

I know this has been asked quite a few times, but i'm having issues with the solutions found on most other pages.
I have a single datagridview column that i want to be sorted by number (1,2,10 instead of 1,10,2)
Best i can see online, i need to convert the column or cell to an integer value type - but i'm not sure how to do so.
I've tried grid.columns(4).valuetype = typeof(System.int32), and tried the same for cells individually.
Trying above always results in a "int32 is a type in 'system' and cannot be used as an expression" error - which i'm not sure about.
The data itself is obtained froma text file, and converted from string to integer when being added into the cell datagrid_alltracks.Rows(shutupaboutlambda).Cells(4).Value = CInt(numstring))

You can just set the DataGridView SortCompare Event to compare two integers (or two singles, or two doubles). Code wise (calling your datagridview "grid")
Private Sub grid_SortCompare(sender as Object, e as DataGridViewSortCompareEventArgs) Handles grid.SortCompare
e.SortResult = CInt(e.cellvalue1).CompareTo(CInt(e.cellValue2))
e.Handled = True
End Sub
if you are doing single or double variables, use CSng or CDbl instead of CInt
e.SortResult = CSng(e.cellvalue1).Compareto(CSng(e.CellValue2)
You can do more fancy sorting if you want, You basically need to know that e.SortResult is Positive, Negative or Zero, and your cells are sorted according to that result (Positive keep order, negative reverse order, Zero - matched - do nothing (yet)). The current row index(s) and column are available in the e arguments so you can also compare adjacent column data if the current cells are matched)

If the grid is bound to a data source you could try
datatable.Columns.Add("ColumnName", GetType(Integer))
Else you may need to use the SortCompare event on the gridview.
See here

I know I'm coming to the party late, but I found that I once I had added the data, I needed to convert the desired columns to have a data type.
I'm adding data like the following:
DataGridView1.Rows.Add(New String() {CInt(recordnum), True, "play", wszName.ToString, qiOffset.ToString, value.ToString, qiLength.ToString})
Then, after all the data has been added, I then do a simple loop and convert the column, where I can then sort it. It's set up so you can do multiple columns if need be.
Dim colnum As Integer
colnum = 0 ' set this as your column to change the data type to
For i As Integer = 0 To DataGridView1.Rows.Count - 1
Dim d As Double = Double.Parse(DataGridView1.Rows(i).Cells(colnum).Value.ToString())
DataGridView1.Rows(i).Cells(colnum).Value = CInt(d)
DataGridView1.Rows(i).Cells(colnum).ValueType = GetType(Double)
Next
Sorting can work for whatever column you adjusted. In this case, it's column 4.
DataGridView1.Sort(DataGridView1.Columns(4), System.ComponentModel.ListSortDirection.Ascending)

Related

Refined list sorting by substring integer after alphabetical sorting

I have some information in a list (called listLines). Each line below is in a List(Of String).
1|This is just a header
3|This is just a footer
2|3456789|0000000|12312312313|BLUE|1|35.00
2|7891230|0000000|45645645655|BLUE|1|22.00
2|7891230|0000000|45645645658|RED|2|13.00
2|3456789|0000000|12312312316|RED|2|45.00
2|3456789|0000000|12312312317|YELLOW|5|-9.00
2|3456789|0000000|12312312315|ORANGE|3|15.00
2|7891230|0000000|45645645659|YELLOW|5|32.00
2|3456789|0000000|12312312314|GREEN|4|-20.00
2|7891230|0000000|45645645656|GREEN|4|39.00
2|7891230|0000000|45645645657|ORANGE|3|-18.50
I'm doing a listLines.sort() on the list to sort it alphabetically. Below is what I get after the .sort().
1|This is just a header
2|3456789|0000000|12312312313|BLUE|1|35.00
2|3456789|0000000|12312312314|GREEN|4|-20.00
2|3456789|0000000|12312312315|ORANGE|3|15.00
2|3456789|0000000|12312312316|RED|2|45.00
2|3456789|0000000|12312312317|YELLOW|5|-9.00
2|7891230|0000000|45645645655|BLUE|1|22.00
2|7891230|0000000|45645645656|GREEN|4|39.00
2|7891230|0000000|45645645657|ORANGE|3|-18.50
2|7891230|0000000|45645645658|RED|2|13.00
2|7891230|0000000|45645645659|YELLOW|5|32.00
3|This is just a footer
With that said, I need to output this information to a file. I'm able to do this ok. I still have a problem though. There is a sequence number in the above data at position 5 just after the listed colors (RED, BLUE, ETC..) that you can see. It's just before the last value which is a decimal type.
I need to further sort this list, keeping it in alphabetical order since position 2 is an account number and I want to keep the account numbers grouped together. I just want them to be resorted in sequential order based on the sequence number.
I was looking at another thread trying to figure out how I can do this. I found a piece of code like listLines.OrderBy(Function(q) q.Substring(35)).ToArray. I think this would probably help me if this was a fixed length file, it isn't however. I was thinking I can do some kind of .split() to get the 5th piece of information and sort it but then it's going to unalphabetize and mix the lines back up because I don't know how to specify to still keep it alphabetical.
Right now I'm outputting my alphabetical list like below so I can format it with commas and double quotes.
For Each listLine As String In listLines
strPosition = Split(listLine, "|")
Dim i As Integer = 1
Dim iBound As Integer = UBound(strPosition)
Do While (i <= iBound)
strOutputText = strOutputText & Chr(34) & strPosition(i) & Chr(34) & ","
i += 1
Loop
My main question is how do I re-sort after .sort() to then get each account (position1) in sequential order (position 5)? OR EVEN BETTER, how can I do both at the same time?
The List(Of T) class has an overload of the Sort method that takes a Comparison(Of T) delegate. I would suggest that you use that. It allows you to write a method or lambda expression that will take two items and compare them any way you want. In this case, you could do that like this:
Dim items = New List(Of String) From {"1|This Is just a header",
"3|This Is just a footer",
"2|3456789|0000000|12312312313|BLUE|1|35.00",
"2|7891230|0000000|45645645655|BLUE|1|22.00",
"2|7891230|0000000|45645645658|RED|2|13.00",
"2|3456789|0000000|12312312316|RED|2|45.00",
"2|3456789|0000000|12312312317|YELLOW|5|-9.00",
"2|3456789|0000000|12312312315|ORANGE|3|15.00",
"2|7891230|0000000|45645645659|YELLOW|5|32.00",
"2|3456789|0000000|12312312314|GREEN|4|-20.00",
"2|7891230|0000000|45645645656|GREEN|4|39.00",
"2|7891230|0000000|45645645657|ORANGE|3|-18.50"}
items.Sort(Function(x, y)
Dim xParts = x.Split("|"c)
Dim yParts = y.Split("|"c)
'Compare by the first column first.
Dim result = xParts(0).CompareTo(yParts(0))
If result = 0 Then
'Compare by the second column next.
result = xParts(1).CompareTo(yParts(1))
End If
If result = 0 Then
'Compare by the sixth column last.
result = xParts(5).CompareTo(yParts(5))
End If
Return result
End Function)
For Each item In items
Console.WriteLine(item)
Next
If you prefer a named method then do this:
Private Function CompareItems(x As String, y As String) As Integer
Dim xParts = x.Split("|"c)
Dim yParts = y.Split("|"c)
'Compare by the first column first.
Dim result = xParts(0).CompareTo(yParts(0))
If result = 0 Then
'Compare by the second column next.
result = xParts(1).CompareTo(yParts(1))
End If
If result = 0 Then
'Compare by the sixth column last.
result = xParts(5).CompareTo(yParts(5))
End If
Return result
End Function
and this:
items.Sort(AddressOf CompareItems)
Just note that this is rather inefficient because it splits both items on each comparison. That's not a big deal for a small list but, if there were a lot of items, it would be better to split each item once and then sort based on those results.

Reference another cell in the same row having calculated the minimum value in a datatable column

Using VB, I've used the following line to successfully find the minimum value in a specific column (say column 5, where the values are all of double) in a datatable:
Dim test as double
test = datatable.Compute("min(sourcecolumn)", "")
I would now like to refer to the values in other columns (let's say column 2) along the row containing that minimum column value.
Any help would be much appreciated as I can't get my head round it!
Thanks
You can use the DataTable.Select() method to get the row(s) that contain the minimum value. DataTable.Select() returns a DataRow(). In the code below, I assumed only one column contains the minimum value hence Data(0).
Dim test as double
test = datatable.Compute("min(sourcecolumn)", "")
Dim Data() As DataRow = datatable.Select("sourcecolumn = " & test.ToString())
Dim column2 = Data(0)(1)
All you have is a value but you currently have no idea what row(s) contain that value. You can use the table's Select method to get the row(s) that contain that value in that column. Once you have the row(s), you can do whatever you want with it/them:
Dim minValue = CDbl(myDataTable.Compute("MIN(MyColumn)", Nothing))
Dim rows = myDataTable.Select($"MyColumn = {minValue}")
For Each row In rows
'Use row here.
Next
Select always returns an array, even if there is only one row, so the loop will always work. If you know that there will never be more than one match, you can just get the first element directly.

Determine if an integer is a member of a list of integers

I need to determine if a particular integer does not exists in a datagridview column. I assume I should create an array of the integers from the dgv column, and then compare if the integer exists in the array. However, there is perhaps an easier or simpler way.
I have looked at many articles but none of them resolve my task. Some of the Stack Overflow articles show similar solutions but I can't quite determine what to do.
For a = 0 To Dgv1.RowCount - 1
If Not Dgv1(1, a).Value = Dgv0(1, m).Value Then
Dgv0(1, Dgv0.RowCount - 1).Value = Dgv0(1, m).Value
End If
Next
I hope to compare an integer with a column of integers in a datagridview and if it is present do nothing but if is not present add it to the datagrid view
Are you using wpf? If yes, create a model.
provide a checking mechanism at the setter, use observablecollection or list then bind it to the datagirdview
Get the row and column of the datagridview
then compare (means condtional statement) to the variable you wanna check
and of course it should be inside of loop, loop count is equal to the count of rows you have in the datagridview.
Here's an example code:
Dim column As String = "YourColumnNameHere"
' Assuming 2 is the number you wanna compare
Dim value As Integer = 2
For row As integer = 0 to dataGridView.RowCount - 1
If dataGridView.Rows(row).Cells(column).Value = value Then
' Do something here
Else
' Do something here
End If
Next

I was trying to Change the row color in DataGridView based on the quantity of a cell value but first row doesn't change [duplicate]

I am using vb.net and I have data coming into my DGV and I have a column labeled deployed if it's a '1', I want to have all the rows with '1' in the deployed column RED and if it's a '0', I want all the rows to be GREEN. This is my method I have, right now the column is the 10th column but it doesn't like the = operator. Even when I use quotes on the 1 with equals compare operator for strings. Should be an integer but I was trying every way I could to see why it's not working.
Private Sub LaptopGrid_CellFormatting(ByVal Sender As Object, ByVal e As System.Windows.Forms.DataGridViewCellFormattingEventArgs) Handles LaptopGrid.CellFormatting
For i As Integer = 0 To LaptopGrid.Rows.Count - 1
If LaptopGrid.Rows(i).Cells(9).Value = 1 Then
LaptopGrid.RowsDefaultCellStyle.BackColor = Color.Green
End If
Next
End Sub
There are a couple of issues with your code.
First, you are handling the CellFormatting event but you are iterating every row to set the backcolor. That event is meant for you to do something to a single, specific cell, the one in question is indicated in the event args: e.RowIndex and e.ColumnIndex. Using a loop, you are acting on many more rows than needed and doing so over and over.
Second, VB has data types. Int32 is one type, String is another, and Object is yet another. You need to convert one type to the other type before you compare. LaptopGrid.Rows(r).Cells(c).Value returns Object (since a cell can literally hold anything), so to compare to 1 you need to convert it to integer.
Finally, you may not want the CellFormatting event for this. If the cell in question is not on screen, the event wont fire (perhaps the user resized the columns). RowPrePaint on the other hand will fire when the row scrolls into view.
Private Sub dgv1_RowPrePaint(sender As Object,
e As DataGridViewRowPrePaintEventArgs) Handles dgv1.RowPrePaint
' dont do the NewRow
If e.RowIndex < 0 OrElse dgv1.Rows(e.RowIndex).IsNewRow Then Return
' convert to int32, then compare
' act on just this row - e.RowIndex
If Convert.ToInt32(dgv1.Rows(e.RowIndex).Cells(3).Value) > 3 Then
dgv1.Rows(e.RowIndex).DefaultCellStyle.BackColor = Color.LemonChiffon
Else
dgv1.Rows(e.RowIndex).DefaultCellStyle.BackColor = Color.MistyRose
End If
End Sub
If the user can edit that cell value, you will want to update the back color accordingly.

VBA: Syntax for dynamic CountIf Ranges

I'll do my best to try and explain my problem, but it's still a bit fuzzy in my mind so this might not be as clear as it should be, for which I apologize in advance.
Here's the part of my code I'm having trouble with:
If Application.WorksheetFunction.countif(Range("D:D"), Cells(x, firstcolumn).Value) _
And Application.WorksheetFunction.countif(Range("F:F"), Cells(x, firstcolumn).Value) _
And Application.WorksheetFunction.countif(Range("H:H"), Cells(x, firstcolumn).Value) Then
The idea behind this project is to check if the values in "Cells(x, firstcolumn)" are present in columns D, F and H at the same time, and then paste the values somewhere else.
However the number of columns to check for the "Cells(x, firstcolumn)" values could be changed, so values would need to be checked in any number of columns (2, 10 etc). My code works perfectly for the specified Ranges but if one is missing or more are added then it stops working.
The columns to check against are always offset by 2 from the firstcolumn and firstcolumn is always B, it will be checked against D, F, H and so on while columns C,E,G etc have other data not relevant for this part.
My best guess is to have the countif Ranges changed dynamically but I'm at a loss of when and how this should be done...
Could anyone point me towards the right direction in order to achieve this? I can post the full code if needed.
Cheers!
You need to extract a function here. Something like this:
Private Function IsPresentInRange(ByVal source As Range, ByVal value As Variant) As Boolean
IsPresentInRange = Application.WorksheetFunction.CountIf(source, value) > 0
End Function
And then you need a way to figure out what ranges you need to give it for a source parameter - that can be a function of its own, or you can hard-code them somewhere; basically you want to have a concept of a group of ranges to call that function with - this would be the simplest:
Private Function GetSourceRanges() As Collection
Dim result As New Collection
result.Add Range("D:D")
result.Add Range("F:F")
result.Add Range("H:H")
'maintain this list here
Set GetSourceRanges = result
End Function
Ideally you would have some logic coded there, so that you don't need to manually add ranges to that collection every time.
And then you can just iterate these ranges and determine if you get a count > 0 for all of them:
Dim sources As Collection
Set sources = GetSourceRanges
Dim result As Boolean
result = True
Dim sourceRange As Range
For Each sourceRange In sources
result = result And IsPresentInRange(sourceRange, Cells(x, firstcolumn).Value)
Next
If result Then
' whatever you had in that If block
End If