ArgumentOutOfRangeException on Datagridview with 1 row - vb.net

I have a strange problem. I have a form with on it 2 unbounded datagridviews and 2 buttons. With the buttons I switch the rows from 1 datagrid to the other.
In the beginning the left datagrid is filled with a number of rows and the right datagrid is empty. So when I click on the button "Add" the selected row from the left datagrid is removed and added to the right datagrid. With the button "Delete" the selected row of the right datagrid is added back to the left datagrid.
When there is only one row in the right datagrid, and I select it to "delete" it, the row is removed from the right datagrid and added to the left without an exception. Now I have a situation where there is only one row in the left datagrid and when I click "Add" to move it to the right datagrid I get an ArgumentOutOfRangeException (index is out of bounds...)
Below is the code which throws the exception
For i As Integer = DgvLeft.SelectedRows.Count - 1 To 0 Step -1
ind = DgvLeft.SelectedRows(i).Index
If ind > 0 Then
DgvLeft.Rows.RemoveAt(ind)
Else
DgvLeft.Rows.Remove(DgvLeft.SelectedRows(i))
End If
Next
So I store the row index in a variable. The first time I used the RemoveAt function and the exception is thrown. To resolve this I added the If-structure and tried the Remove function. But again the exception is thrown.
I don't understand why the exception is thrown. I use the same code for the "delete" button and there it doesn't happen. Also when I store the RowIndex in a variable the index is known, but not when I try to Remove the row.
Can someone help me with this strange problem?

Since the posted for loop is “broken”, I feel it is unnecessary to question what you are trying to accomplish here. However, given what you described where there are two grids and two buttons (Add/Delete) on a form. When the “Add” button is clicked, it moves the “selected rows” from the “left” grid to the “right” grid, and then deletes the “selected rows” rows from the “left” grid. If the “Delete” button is clicked, then the opposite process happens moving the “selected rows” from the right grid to the left grid, then delete the selected rows from the right grid.
If this is correct, then it appears you are making this more complicated than it has to be. This would be much easier if you used a data source of some form. However, to break the above problem down, it appears that two methods may come in handy for what you want to do. The first one could simply add the selected rows from one given grid to another given grid. The next method would simply delete the selected rows from a given grid. With these two methods implemented, the “add” button would be…
AddSelectedRows(dgvLeft, dgvRight)
RemoveSelectedRows(dgvLeft)
The delete button would be…
AddSelectedRows(dgvRight, dgvLeft)
RemoveSelectedRows(dgvRight)
Before I explain the code below, it should be noted that your current posted code is making a huge programming “no-no” and without question is a problem for you. As Mary and others have pointed out, “changing the value of the counter variable in a loop or (as the code does) changing the collection it is looping through is rarely if ever done.”
Try the code below it may make things easier,
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
AddColumns(dgvLeft)
AddColumns(dgvRight)
FillGrid(dgvLeft)
End Sub
Private Sub FillGrid(dgv As DataGridView)
For i = 0 To 15
dgv.Rows.Add("C0R" + i.ToString(), "C1R" + i.ToString(), "C2R" + i.ToString())
Next
End Sub
Private Sub AddColumns(dgv As DataGridView)
Dim txtCol = New DataGridViewTextBoxColumn()
txtCol.Name = "Col0"
txtCol.HeaderText = "Col 0"
dgv.Columns.Add(txtCol)
txtCol = New DataGridViewTextBoxColumn()
txtCol.Name = "Col1"
txtCol.HeaderText = "Col 1"
dgv.Columns.Add(txtCol)
txtCol = New DataGridViewTextBoxColumn()
txtCol.Name = "Col2"
txtCol.HeaderText = "Col 2"
dgv.Columns.Add(txtCol)
End Sub
Private Sub btnAdd_Click(sender As Object, e As EventArgs) Handles btnAdd.Click
AddSelectedRows(dgvLeft, dgvRight)
RemoveSelectedRows(dgvLeft)
End Sub
Private Sub btnDelete_Click(sender As Object, e As EventArgs) Handles btnDelete.Click
AddSelectedRows(dgvRight, dgvLeft)
RemoveSelectedRows(dgvRight)
End Sub
Private Sub RemoveSelectedRows(dgv As DataGridView)
Dim totalRowsToDelete = dgv.SelectedRows.Count
Dim selectedRow As DataGridViewRow
For i = totalRowsToDelete - 1 To 0 Step -1
selectedRow = dgv.SelectedRows(i)
If (Not selectedRow.IsNewRow) Then
dgv.Rows.RemoveAt(dgv.SelectedRows(i).Index)
End If
Next
End Sub
Private Sub AddSelectedRows(sourceDGV As DataGridView, destinationDGV As DataGridView)
Dim selectedRowCount = sourceDGV.Rows.GetRowCount(DataGridViewElementStates.Selected)
If (selectedRowCount > 0) Then
Dim selectedRow As DataGridViewRow
For i = 0 To selectedRowCount - 1
selectedRow = sourceDGV.SelectedRows(i)
If (Not selectedRow.IsNewRow) Then
destinationDGV.Rows.Add(selectedRow.Cells(0).Value.ToString(),
selectedRow.Cells(1).Value.ToString(),
selectedRow.Cells(2).Value.ToString())
End If
Next
End If
End Sub

You are changing the value of DgvLeft.SelectedRows.Count in your For loop.
Try...
Dim RowCount As Integer = DgvLeft.SelectedRows.Count
For i As Integer = RowCount - 1 To 0 Step -1

I want to thank Mary for her answer, because that made me think about my code. I looked at the code of the "delete" button, where I use the same for loop, and that code doesn't throw an exception. So there must be a difference between the code of my two buttons.
This is an expanded piece of my code in the "add" button:
For i As Integer = DgvLeft.SelectedRows.Count - 1 To 0 Step -1
'fill the array with the row values
For j As Integer = 0 To DgvLeft.Columns.Count - 1
fields(j) = DgvLeft.SelectedRows(i).Cells(DgvLeft.Columns(j).Name).Value
Next
'delete the row in datagrid Left
ind = DgvLeft.SelectedRows(i).Index
If ind > 0 Then
DgvLeft.Rows.RemoveAt(ind)
Else
DgvLeft.Rows.Remove(DgvLeft.SelectedRows(i))
End If
'add the row in datagrid Right
DgvRight.Rows.Add(fields)
Next
In the code snippet above I attempt to remove a row BEFORE I add the array of values from this row to the other datagrid. When I looked at the code of the "delete" button I attempt to remove the row AFTER I add the array of values to the other datagrid.
So I moved this piece of code below the code of adding the array of values and it worked. Probably the index exception is thrown by the array rather than the datagridview.

Related

Select a specific row from ComboBox and add amount in other Column from same row in DataGridView VB.NET

I'm using VB.NET for a small project. I have a DataGridView with some rows and columns.
I want to send amounts inside a specific row & column. For this purpose I'm using a ComboBox to select the receiver, but I cant manage to select the specific row & column for the amount.
Here is a photo of the form:
[
I managed to add the receivers inside the ComboBox and to add an amount, but in all rows.
This is code (it auto generates the rows for now).
Public Class Form1
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Dim rowId As Integer = Me.DataGridView1.Rows.Add()
Dim row As DataGridViewRow = Me.DataGridView1.Rows(rowId)
row.Cells("Column1").Value = "UNITED BANK"
row.Cells("Column2").Value = "1000"
row.Cells("Column3").Value = "ExampleInfo"
Dim rowId2 As Integer = Me.DataGridView1.Rows.Add()
Dim row2 As DataGridViewRow = Me.DataGridView1.Rows(rowId2)
row2.Cells("Column1").Value = "FREE BANK"
row2.Cells("Column2").Value = "2000"
row2.Cells("Column3").Value = "ExampleInfo"
Dim bolAdd As Boolean = False
Dim strValue As String
For Each myRow As DataGridViewRow In Me.DataGridView1.Rows
bolAdd = True
strValue = myRow.Cells("Column1").Value
For Each myItem As String In Me.ComboBox1.Items
If myItem = strValue Then bolAdd = False
Next
If bolAdd AndAlso Not (strValue Is Nothing) Then Me.ComboBox1.Items.Add(strValue)
Next
If Me.ComboBox1.SelectedIndex = -1 Then
Me.ComboBox1.SelectedIndex = 0
End If
End Sub
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
For Each rowX As DataGridViewRow In Me.DataGridView1.Rows
rowX.Cells(1).Value = Val(rowX.Cells(1).Value) + Val(TextBox1.Text)
Next
End Sub
End Class
With this code, I'm able to add an amount, but to all rows.
I want to add an amount only to the row I selected in the ComboBox.
Your current code in the Button1_Click event is adding the amount from the text box to ALL the rows in the grid regardless of “what” value is selected in the combo box.
From what you describe, you want the amount from the text box to be added to rows that “match” the value in the combo box. The current code is never checking this, so the value from the text box is added to all the rows in the grid.
Therefore, the code needs to check to see if the value in Column1 "Receiver" “matches” the value in the combo box. If it DOES match, then add the value from the text box to the current value in Column2 "USD."
It is unclear if there may be more rows in the grid that have the same Column1 "Receiver" value and if you want to update those values also. I am assuming that there will only be ONE row in the grid that “matches” the value in the combo box. Therefore, once we find this value and add the amount, then we are done and the code will return without looping through the rest of the rows in the grid.
So is what you need to do is alter the code in the button click event to do this checking as described above.
There are a lot of checks we need to make. We need to check that the combo box has a value to compare to. Also, before we check ANY cell value, we need to make sure the cells value is not null BEFORE we try and call the cell's Value ToString method.
In addition, the cell MAY have a value, however, it may not be a number, so we have to check and make sure the value in the cell is an actual number and the same would apply to the text box.
So, walking through the code below would go something like…
If (Int32.TryParse(TextBox1.Text, addedValue)) … checks to see if
the text box amount is a valid number.
If (Not String.IsNullOrEmpty(targetComboValue)) … checks to make
sure the combo box value is not empty.
Then we start the loop through all the rows in the grid.
If (Not rowX.IsNewRow) … checks to see if the row is the “new” row
which we will ignore this “new” row.
If (rowX.Cells("Column1").Value IsNot Nothing) … checks to make
sure the “Receiver” cell is not null
If (rowX.Cells("Column1").Value.ToString() = targetComboValue) …
checks to see if the “Receiver” cells value matches the value in the
combo box.
If (rowX.Cells("Column2").Value IsNot Nothing) … check to make sure
the “USD” cell is not null.
And Finally,…
If (Int32.TryParse(rowX.Cells("Column2").Value.ToString(), currentValue)) … check to see if the value in the “USD” cell is a valid number.
If all these checks succeed then add the value from the text box to the current value in the “USD” cell.
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim targetComboValue = ComboBox1.SelectedItem
Dim addedValue As Int32
Dim currentValue As Int32
If (Int32.TryParse(TextBox1.Text, addedValue)) Then
If (Not String.IsNullOrEmpty(targetComboValue)) Then
For Each rowX As DataGridViewRow In Me.DataGridView1.Rows
If (Not rowX.IsNewRow) Then
If (rowX.Cells("Column1").Value IsNot Nothing) Then
If (rowX.Cells("Column1").Value.ToString() = targetComboValue) Then
If (rowX.Cells("Column2").Value IsNot Nothing) Then
If (Int32.TryParse(rowX.Cells("Column2").Value.ToString(), currentValue)) Then
rowX.Cells("Column2").Value = currentValue + addedValue
Return
End If
End If
End If
End If
End If
Next
End If
End If
End Sub
I hope this makes sense.
You can edit the desired row just by using a syntax as follow:
Me.DataGridView1.Rows(1).Cells(2).Value = "your string"
So if you want to select corresponding to your combo index :
Dim myId As Integer = Me.ComboBox1.SelectedIndex
Me.DataGridView1.Rows(myId).Cells(2).Value = "your string"
But this assuming that the count of combo and DataGridView are same.
If not you should think about to add an id Column in your DataGridView ...
If you mean something else please explain, I will edit.

Why does visual basic copy a last empty Column when exporting a DataGridView to Clipboard

Thank you for looking at my problem. ^^
I have a small project with a "Copy to Clipboard" button which doesn't work as intended.
I want to copy all 11 Columns and all 10 Rows but it always adds a 12th empty Column, when I subtract a Column from dgvRandom.ColumnCount than the last Column which is supposed to have content is empty. (Picture to clarify - Green is my intention - Red is the actual state) https://i.stack.imgur.com/zvqpr.png
The code I'm referring to is this:
Private Sub btnExport_Click(sender As Object, e As EventArgs) Handles btnExport.Click
Dim sb As New StringBuilder
For row As Integer = 0 To dgvRandom.RowCount - 1
For col As Integer = 0 To dgvRandom.ColumnCount - 1
sb.Append(dgvRandom(col, row).Value?.ToString)
sb.Append(ControlChars.Tab)
Next
sb.Append(ControlChars.NewLine)
Next
Clipboard.SetDataObject(New DataObject(sb.ToString.Trim))
MsgBox("Copied to Clipboard", MsgBoxStyle.OkOnly)
End Sub
I think it's the final TAB before your line break. To remove it, try changing
sb.Append(ControlChars.Tab)
to
If col<dgvRandom.ColumnCount - 1 then sb.Append(ControlChars.Tab)
or even
If col<dgvRandom.ColumnCount - 1 then
sb.Append(ControlChars.Tab)
else
sb.Append(ControlChars.NewLine)
end if
and then delete the sb.Append(ControlChars.NewLine) after the loop

Removing items from a ListBox in VB.net

I have two ListBox1 and ListBox2. I have inserted items into a ListBox2 with the following code by selecting ListBox1 item:
da6 = New SqlDataAdapter("select distinct(component_type) from component where component_name='" & ListBox1.SelectedItem() & "'", con)
da6.Fill(ds6, "component")
For Each row As DataRow In ds6.Tables(0).Rows
ListBox2.Items.Add(row.Field(Of String)("component_type"))
Next
But when I reselect another item of ListBox1 then ListBox2 shows preloaded items and now loaded item together.
I want only now loaded item to be displayed in listbox.
I used this code but problem not solved:
For i =0 To ListBox2.items.count - 1
ListBox2.Items.removeAt(i)
Next
OR
listbox2.items.clear() is also not working..
How can I clear all items in the ListBox2?
Use simply:
ListBox2.Items.Clear()
To take your last edit into account: Do that before you add the new items
MSDN: ListBox.ObjectCollection.Clear
Removes all items from the collection.
Note that the problem with your approach is that RemoveAt changes the index of all remaining items.
When you remove an item from the list, the indexes change for
subsequent items in the list. All information about the removed item
is deleted. You can use this method to remove a specific item from the
list by specifying the index of the item to remove from the list. To
specify the item to remove instead of the index to the item, use the
Remove method. To remove all items from the list, use the Clear
method.
If you want to use RemoveAt anyway, you can go backwards, for example with:
a for-loop:
For i As Int32 = ListBox2.Items.Count To 0 Step -1
ListBox2.Items.RemoveAt(i)
Next
or a while
While ListBox2.Items.Count > 0
ListBox2.Items.RemoveAt(ListBox2.Items.Count - 1)
End While
old C# code
for (int i = ListBox2.Items.Count - 1; i >= 0; i--)
ListBox2.Items.RemoveAt(i);
while(ListBox2.Items.Count > 0)
ListBox2.Items.RemoveAt(ListBox2.Items.Count - 1);
This code worked for me:
ListBox1.Items.RemoveAt(ListBox1.SelectedIndex)
If you only want to clear the list box, you should use the Clear (winforms | wpf | asp.net) method:
ListBox2.Items.Clear()
There is a simple method for deleting selected items, and all these people are going for a hard method:
lstYOURVARIABLE.Items.Remove(lstYOURVARIABLE.SelectedItem)
I used this in Visual Basic mode on Visual Studio.
Here's the code I came up with to remove items selected by a user from a listbox It seems to work ok in a multiselect listbox (selectionmode prop is set to multiextended).:
Private Sub cmdRemoveList_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles cmdRemoveList.Click
Dim knt As Integer = lstwhatever.SelectedIndices.Count
Dim i As Integer
For i = 0 To knt - 1
lstwhatever.Items.RemoveAt(lstwhatever.SelectedIndex)
Next
End Sub
Already tested by me, it works fine
For i =0 To ListBox2.items.count - 1
ListBox2.Items.removeAt(0)
Next
I think your ListBox already clear with ListBox2.Items.Clear(). The problem is that you also need to clear your dataset from previous results with ds6.Tables.Clear().
Add this in your code:
da6 = New SqlDataAdapter("select distinct(component_type) from component where component_name='" & ListBox1.SelectedItem() & "'", con)
ListBox1.Items.Clear() ' clears ListBox1
ListBox2.Items.Clear() ' clears ListBox2
ds6.Tables.Clear() ' clears DataSet <======= DON'T FORGET TO DO THIS
da6.Fill(ds6, "component")
For Each row As DataRow In ds6.Tables(0).Rows
ListBox2.Items.Add(row.Field(Of String)("component_type"))
Next
This worked for me.
Private Sub listbox_MouseDoubleClick(sender As Object, e As MouseEventArgs)
Handles listbox.MouseDoubleClick
listbox.Items.RemoveAt(listbox.SelectedIndex.ToString())
End Sub
This can be also checked. Selected item from the list can be deleted using for loop, but error occurs as soon as list item removed to avoid this problem just jump from the loop and start fresh selected list to be remove.
Private Sub BtnDelete_ClickButtonArea(Sender As Object, e As MouseEventArgs) Handles BtnDelete.ClickButtonArea
If LstTest.SelectedIndex > -1 Then
jmp1:
If LstTest.SelectedItems.Count > 0 Then
For i = 0 To LstTest.SelectedItems.Count - 1
For Each SI In LstTest.SelectedItems
LstTest.Items.Remove(SI)
GoTo jmp1
Next
Next
End If
End If
End Sub
Dim ca As Integer = ListBox1.Items.Count().ToString
While Not ca = 0
ca = ca - 1
ListBox1.Items.RemoveAt(ca)
End While

Getting the column names of the selected row in SQL?

I have a program with 2 forms and both forms have their own grid. Grid1 is on form1, Grid2 is on form2. I want Grid2 to serve as a pop up window which is used for editing. When a user doubleclicks a row on Grid1, it should do a requery on just that row and display that single row on Grid2. I can't figure out how to get the column names of the selectedrow then do a query on it's unique identifers. What I have so far:
(This is on form1 by the way)
Public Sub dgvForm1_CellDoubleClick(ByVal sender As Object, ByVal e As System.Windows.Forms.DataGridViewCellEventArgs) Handles dgvForm1.CellDoubleClick
Dim i As Integer = 0
Dim rowIndex As Integer
Dim cellName as string
While dgvForm1.SelectedRows.Item(0).Cells(i).ColumnIndex < dgvForm1.ColumnCount
rowIndex = dgvForm1.SelectedRows.Item(0).HeaderCell.RowIndex
cellName = dgvForm1.SelectedRows.Item(0).HeaderCell.toString
Select Case cellName
Case "control_no"
Dim sControlNum = cellName.ToString
Case "store_id"
Dim sStoreNum = cellName.ToString
End Select
i = i + 1
End While
end sub
I simply just want the user to doubleclick a row that is displayed on a datagridview with many other records, and that triggers a doubleclick event. This would then loop through the selectedrow's column names until it finds both control_id and store_id and gets their values. P.s. I tried google for a good hour or so but I had a hard time getting any techniques to work.
UPDATE:
I should probably add, this is a bound datagrid that uses sqlClient and its dataadapter/dataset method.
myTable.Columns.ColumnName.
Read more at http://msdn.microsoft.com/en-us/library/system.data.datacolumn.columnname.aspx

How to Implement Rowspan in a GridView (for .NET 3.5)

I created a custom GridView (actually a RadGrid) which does roughly the following.
Initialize GridView formatting
Setup Columns (depending on type of data to be displayed)
Populate the rows
When Instantiating this GridView, and adding it to my page, I want the first column to contain a "rowspan" attribute on the first row of repeated, but similar data. The "rowspan" value should be equal to the number of similar rows following it. In this way I hope to make the final view cleaner.
The logic for this, I figure, should take place while populating the rows. Initially I add rows to a DataTable and then bind it to the GridView as the final step.
Here's the general logic I was attempting, but it didn't work for me. What am I doing wrong?
Dim dt As New DataTable() 'Has three default columns
For Each d In Documents 'MAIN ITEM (First Column Data)
Dim rowspan As Integer
rowspan = 0
For Each f In Files
If rowspan = 0 Then
Me.dt.Rows.Add(New Object() {d.Title, f.Language, f.FileSize})
'THIS DOESN'T WORK!
Me.dt.Columns(0).ExtendedProperties.Item("rowspan") = rowspan.ToString()
Else
Me.dt.Rows.Add(New Object() {Nothing, f.Language, f.FileSize})
End If
rowspan += 1
Next
Next
Also keep in mind that the this is dumped into a DataView which is sorted by the first column, so I would imagine it must actually be sorted first, then somehow count the rows for each "like" first column, then set the "rowspan" value for the first row of that column equal to the number of rows belonging to it.
Does this make sense? Here is an ideal example of what I would like to accomplish in the layout:
Try this.
Protected Sub DemoGrid_PreRender(sender As Object, e As System.EventArgs) Handles DemoGrid.PreRender
MergeRowsWithSameContent(sender)
End Sub
Public Sub MergeRowsWithSameContent(gvw As GridView)
For rowIndex As Integer = gvw.Rows.Count - 2 To 0 Step -1
Dim row As GridViewRow = gvw.Rows(rowIndex)
Dim previousRow As GridViewRow = gvw.Rows(rowIndex + 1)
For i As Integer = 0 To row.Cells.Count - 1
If row.Cells(i).Text = previousRow.Cells(i).Text Then
row.Cells(i).RowSpan = If(previousRow.Cells(i).RowSpan < 2, 2, previousRow.Cells(i).RowSpan + 1)
previousRow.Cells(i).Visible = False
End If
Next
Next
End Sub
P.S: Shameless port of this wonderful code I have been using for years.