vb.net getting a collection of rows from a bound datagridview based on selection - vb.net

I've read around and googled but didn't find the little thing which would help me. I think I've got some simple logical error.
Ok, so here is what I have:
A DataGridView bound to a Datatable populated with filenames (and other info, mainly audio format).
Well, now I've written a sub (VS2013 and using .Net 4.0) for printing which works fine, I tried to add an option to print only selected rows from the DataGridView and thought ok, then I give the printing routine the whole rows of the DataGridView or only the selected rows based on that option.
This is what I tried:
On the form Level I defined the rowCollection as:
Dim rowCollection As DataGridViewRowCollection
The BeginPrint Sub then has the following lines to get the rows:
'*** Get Rows to print
rowCollection = New DataGridViewRowCollection(FileDataGridView)
If My.Settings.PrintSelected And (FileDataGridView.SelectedRows.Count > 0) Then
Dim row As New DataGridViewRow
For r = FileDataGridView.SelectedRows.Count - 1 To 0 Step -1
row = FileDataGridView.SelectedRows(r)
rowCollection.Add(FileDataGridView.Rows(row.Index))
Next
Else
rowCollection = FileDataGridView.Rows
End If
I thought a rowCollection would be something like an array of the single rows,
but apparently this doesn't do the job.
If I want to print all rows, this works fine, but for the selected rows, I get an error the no elements could be added to the row collection because the DataGridView is databound. I thought the rowCollection could be population by single rows, an not only link to the whole DataGridView. Where am I wrong?
I also tried to get only the .SelectedRows as a collection but then I get type errors as DataGridView.Rows and DataGridView.SelectedRows do not have the same format?
I would like to get a list/collection/array of the rows or selected rows, which I then can use for printing.
Any help would be appreciated
regards,
Christian

Surely not a perfect solution but does the job:
Dim rows As List(Of DataGridViewRow)
Dim rowarray(DataGridView1.Rows.Count) As DataGridViewRow
If DataGridView1.SelectedRows.Count > 0 Then
DataGridView1.SelectedRows.CopyTo(rowarray, 0)
Else
DataGridView1.Rows.CopyTo(rowarray, 0)
End If
rows = rowarray.ToList
rows.RemoveAll(Function(a As DataGridViewRow) a Is Nothing)
There is a difference between a SelectedRowCollection and a RowCollection, for what ever reason they didn't used inheritence for those 2, so you need to cheat a little to process both the same way

Related

DataGridView Remove Error

I want to delete the duplicate value and blank lines in the DataGridView.
I am using the following code for this.
Private Sub dgwsil()
On Error Resume Next
For i2 As Integer = DataGridView1.RowCount - 1 To 0 Step -1
If Trim(DataGridView1.Rows(i2).Cells(0).Value.ToString()) = "" Then
DataGridView1.Rows.RemoveAt(i2)
End If
Next
Dim numberOfRows = DataGridView1.Rows.Count
Dim i As Integer = 0
While i < numberOfRows - 2
For ii As Integer = (numberOfRows - 1) To (i + 1) Step -1
If DataGridView1.Rows(i).Cells(0).Value.ToString() = DataGridView1.Rows(ii).Cells(0).Value.ToString() Then
DataGridView1.Rows.Remove(DataGridView1.Rows(ii))
numberOfRows -= 1
End If
Next
i += 1
End While
End Sub
Code sometimes works fine.
But sometimes it gives an error
How can I solve this problem ? Or is there any code you use for it?
It looks like you are going into this problem with a VBA mindset. In VBA, you would usually manipulate the datagridview directly and the On Error Resume Next line would be acceptable.
In VB.NET you don't want to do either of those things. Your datagridview should be bound to a collection of information. That collection can be a datatable or an array or any type of collection really. So instead of using a loop to manipulate the datagridview directly you would either loop through the backing collection or better yet LINQ through the collection and manipulate it however you need. There are a ton of tutorials out there explaining everything you could ever need so googling around for how to bind a datatable to a datagridview should get you what you need.
I would also suggest looking into the Try...Catch...Finally... structure. You should really use that to handle any errors that could possibly come up from your code.

How to copy multiple records from a datagridview into another form using VB

I've a project that I am working on which I intend to copy from the rows selected just the oldcomputername column value and pass to a task form. On the Task from I've created a textbox (Number of systems)to display the number of oldcomputername copied from the datagridview in table (inventory). from my code, I can only copy a single record and I'd like to copy based on how many records are selected. Can anyone help me.
Thank you in advance for your help.
Here is my copy for the Assign Task button:
Try
Me.Hide()
Dim MyAssignments As New MyAssignments
MyAssignments.ShowDialog()
Catch ex As Exception
End Try
For the Task form load event I have this code:
Dim a As Integer
a = frmInventoryTest.InventoryDGV.CurrentRow.Index
Me.TaskNameTextBox.Text = frmInventoryTest.InventoryDGV.Item(1, a).Value.ToString
Based on my comment, the code could look something like this:
Dim lines As New List(Of String)
For Each row As DataGridViewRow In myDataGridView.SelectedRows
lines.Add(row.Cells(1).Value.ToString())
Next
myTextBox.Lines = lines.ToArray()

Detect rows in DataGridView whose cell values have been changed by user

I have a VB.NET WinForms project that I'm building in VS2013. In a DataGridView bound to a DataSet I want to highlight each row that the user has changed - and if the user changes a row's contents back to its original values (as compared to the database values) I want to remove the highlighting.
I have been Googling for a while now and really haven't gotten anywhere.
All I know at this point is that EmployeesDataSet.HasChanges(DataRowState.Modified) returns False in the CellValueChanged event after having changed text in a cell and clicked out of the row.
My assumption is that the overall method would be something like on KeyUp event compare the current row's cell values to the DataSet (or BindingSource or TableAdapter?) and if anything is different, highlight the row, otherwise set the row to the default backcolor.
But if that's the right approach I don't understand what I would compare the row's contents to. Would it be the DataSet? The TableAdapter? The BindingSource? If it's one of those, how to I compare the correct row?
UPDATE
Some more research has made some progress:
I found this iteration code:
Dim dsChanged As DataSet = EmployeesDataSet.GetChanges()
For Each dt As DataTable In dsChanged.Tables
For Each row As DataRow In dt.Rows
For i As Integer = 0 To dt.Columns.Count - 1
Dim currentBackColor As System.Drawing.Color = dgvEmployees.AlternatingRowsDefaultCellStyle.BackColor
If Not row(i, DataRowVersion.Current).Equals(row(i, DataRowVersion.Original)) Then
dgvEmployees.Rows(dt.Rows.IndexOf(row)).DefaultCellStyle.BackColor = Color.LightPink
Else
' No changes so set it to its original color
dgvEmployees.Rows(dt.Rows.IndexOf(row)).DefaultCellStyle.BackColor = currentBackColor
End If
Next
Next
Next
I put this in a separate Sub, which is being called in the DataGridView.CellValueChanged event.
That correctly detects the rows that have changed cell values, but my code to color the background isn't quite right. As is, it is coloring each successive row from top to bottom as I make changes - regardless of what row in the DGV I edit.
I assumed that dt.Rows.IndexOf(row) would correctly get the correct index of the DGV, since I'm iterating through the DGV's DataTable.
Well, if you go hunting long enough and spend enough time trying different things, you'll eventually find an answer...
Here's the working code I ended up with:
Private Sub dgvEmployees_CellValueChanged(sender As Object, e As DataGridViewCellEventArgs) Handles dgvEmployees.CellValueChanged
' Pass the row and cell indexes to the method so we can change the color of the correct row
CompareDgvToDataSource(e.RowIndex, e.ColumnIndex)
End Sub
Private Sub CompareDgvToDataSource(ByVal rowIndex As Integer, ByVal columnIndex As Integer)
If Not dgvEmployees Is Nothing And dgvEmployees.Rows.Count > 0 Then
' Condition required because this Method is also called when the DGV is being built and populated
Console.WriteLine("rowIndex: " & rowIndex.ToString() & ", columnIndex: " & columnIndex.ToString() & ", cell value: " & dgvEmployees.Rows(rowIndex).Cells(columnIndex).Value.ToString())
End If
' Force ending Edit mode so the last edited value is committed
EmployeesBindingSource.EndEdit()
Dim dsChanged As DataSet = EmployeesDataSet.GetChanges()
If Not dsChanged Is Nothing Then
For Each dt As DataTable In dsChanged.Tables
For Each row As DataRow In dt.Rows
For i As Integer = 0 To dt.Columns.Count - 1
If Not row(i, DataRowVersion.Current).Equals(row(i, DataRowVersion.Original)) Then
Console.WriteLine("Row index: " & dt.Rows.IndexOf(row))
dgvEmployees.Rows(rowIndex).DefaultCellStyle.BackColor = Color.LightPink
End If
Next
Next
Next
End If
End Sub
A couple of notes:
Without calling EndEdit() on the BindingSource the changes won't be detected since this is being called by the CellValueChanged, which happens before the BindingSource is changed.
I tried adding an Else clause to set the BackColor to the original color (for when the DGV row is detected to be the same as the DataSet's row or when validation fails), but I can't figure out how to account for the DGV.AlternatingRowsDefaultCellStyle.BackColor property being set. Ideas???
I think this could be improved by, since I have the row and column indexes in the Method, just going directly to the DataSet's/DataTable's corresponding row and comparing just that, instead of iterating through the entire DataSet. Ideas on that one would be appreciated, but I'll do some more testing to see if I can get it (I figured it out myself this far...)

Copy contents of returned SQL Query row to a Listbox or Array

tl;dr Method of copying contents of an SQL query to an array or a string or a listbox. Single columns, multiple rows.
Working on a small project for some extra course credit.
Developing it currently in Visual Studio 2010.
Essentially is an interactive menu where users select items and can add them to an inbuilt
list and it will calculate total nutritional information and costs etc..
I'm having an issue however. When the user reaches the order builder page they can select the type of item they wish to buy.
E.G.
Beef
Clicking this should then populate a list box with all the related items.
I'm hoping to do this via a database connection.
I currently have an embedded database.
Their where 2 ways I've tried to do this but both proved unsuccessful or perhaps I'm just doing it wrong.
First method.
Dim index As Integer = 0
Dim length As Integer = adapter.productscounter()
' Small query that works out total number of rows.
For index = 0 To length
ListBox1.Items.Add(adapter.SelectBeef(index))
Next
This gives me the error:
There is no row at position 0.
which I seem unable to solve. The query runs upon trial execution and theirs something their.
Index out of range exception
The other method I was attempting was similar code however using an array and then copying the contents of that into the listbox.
Dim index As Integer
Dim test(5)
Dim length As Integer = adapter.productscounter()
Dim counter As Integer
For index = 0 To length
test(index) = adapter.SelectChicken()
counter = counter + 1
Next
For counter = 0 To length
ListBox1.Items.Add(test(index))
Next
Generates:
Argument nullexception
Value cannot be null.
Parameter name: item.
In Visual Basic, the standard is to start a list with element 1, not element 0. You could try:
For index = 1 To length
ListBox1.Items.Add(adapter.SelectBeef(index))
Next
Though of course, I have no insight in what the SelectBeef method does.

ComboBox DataBinding DisplayMember and LINQ queries

Update
I decided to iterate through the Data.DataTable and trimmed the values there.
Utilizing SirDemon's post, I have updated the code a little bit:
Sub test(ByVal path As String)
Dim oData As GSDataObject = GetDataObj(path)
EmptyComboBoxes()
Dim oDT As New Data.DataTable
Try
Dim t = From r In oData.GetTable(String.Format("SELECT * FROM {0}gsobj\paths ORDER BY keyid", AddBS(path))) Select r
If t.Count > 0 Then
oDT = t.CopyToDataTable
For Each dr As Data.DataRow In oDT.Rows
dr.Item("key_code") = dr.Item("key_code").ToString.Trim
dr.Item("descript") = dr.Item("descript").ToString.Trim
Next
dataPathComboBox.DataSource = oDT
dataPathComboBox.DisplayMember = "descript"
dataPathComboBox.ValueMember = "key_code"
dataPathComboBox.SelectedIndex = 0
dataPathComboBox.Enabled = True
End If
Catch ex As Exception
End Try
End Sub
This works almost as I need it to, the data is originally from a foxpro table, so the strings it returns are <value> plus (<Field>.maxlength-<value>.length) of trailing whitespace characters. For example, a field with a 12 character length has a value of bob. When I query the database, I get "bob_________", where _ is a space.
I have tried a couple of different things to get rid of the whitespace such as:
dataPathComboBox.DisplayMember.Trim()
dataPathComboBox.DisplayMember = "descript".Trim.
But nothing has worked yet. Other than iterating through the Data.DataTable or creating a custom CopyToDataTable method, is there any way I can trim the values? Perhaps it can be done in-line with the LINQ query?
Here is the code I have so far, I have no problem querying the database and getting the information, but I cannot figure out how to display the proper text in the ComboBox list. I always get System.Data.DataRow :
Try
Dim t = From r In oData.GetTable("SELECT * FROM ../gsobj/paths ORDER BY keyid") _
Select r
dataPathComboBox.DataSource = t.ToList
dataPathComboBox.SelectedIndex = 0
'dataPathComboBox.DisplayMember = t.ToList.First.Item("descript")
dataPathComboBox.Enabled = True
Catch ex As Exception
Stop
End Try
I know that on the DisplayMember line the .First.Item() part is wrong, I just wanted to show what row I am trying to designate as the DisplayMember.
I'm pretty sure your code tries to set an entire DataRow to a property that is simply the name of the Field (in a strongly type class) or a Column (in a DataTable).
dataPathComboBox.DisplayMember = "descript"
Should work if the DataTable contains a retrieved column of that name.
Also, I'd suggest setting your SelectedIndex only AFTER you've done the DataBinding and you know you actually have items, otherwise SelectedIndex = 0 may throw an exception.
EDIT: Trimming the name of the bound column will trim just that, not the actual bound value string. You either have to go through all the items after they've been bound and do something like:
dataPathComboBox.Item[i].Text = dataPathComboBox.Item[i].Text.Trim()
For each one of the items. Not sure what ComboBox control you're using, so the item collection name might be something else.
Another solution is doing that for each item when it is bound if the ComboBox control exposes an onItemDataBound event of some kind.
There are plenty of other ways to do this, depending on what the control itself offers and what you choose to do.
DisplayMember is intended to indicate the name of the property holding the value to be displayed.
In your case, I'm not sure what the syntax will by since you seem to be using a DataSet, but that should be
... DisplayMember="Item['descript']" ...
in Xaml, unless you need to switch that at runtime in which case you can do it in code with
dataPathComboBox.DisplayMember = "Item['descript']"
Again, not 100% sure on the syntax. If you are using a strongly typed DataSet it's even easier since you should have a "descript" property on your row, but given hat your error indicates "System.DataRow" and not a custom type, I guess you are not.
Because I can't figure out the underlying type of the datasource you are using I suggest you to change commented string to
dataPathComboBox.DisplayMember = t.ElementType.GetProperties.GetValue(0).Name
and try to determine correct index (initially it is zero) in practice.