Pasting Excel Clipboard Text to Databound DataGridView and Entity Framework Validation - vb.net

In a Windows Forms project being developed in Visual Studio 2010, I have a DataGridView bound to a BindingSource whose DataSource is a BindingList(Of T). T is an entity from an Entity Framework 5 model.
My entities implement INotifyPropertyChanged and IDataErrorInfo.
My users are Excel heads and insist that I provide for the pasting of Excel data into the grids in use in our application.
So, I set out with a couple of simple rules.
Mimic as closely as possible the copy & paste behavior within Excel.
Pasting data into a new row in a DataGridView should create and validate new entities of the type represented in the grid.
I've come a long way with this but have now bumped up against something I can't figure out.
Judging by what information I can find, it seems clear that when pasting into a bound grid that the underlying data source should be the target of your edits and creates.
Or should it?
I've tried both ways.
When targeting the cells themselves, I hoped that I could write the routine such that the validation events built into DataGridView would fire when needed, whether I was editing an existing row or creating a new one.
I shortly discovered that it wasn't working as expected because CellValidating doesn't fire until the cell loses focus.
While pasting, I'd like to validate the cell at the moment a value is pasted into it - cancelling the rest of the paste operation if it fails.
When targeting the underlying data source (a row's DataBoundItem cast as the appropriate entity type), I can create new entities from the clipboard data and validate them before committing changes to the DbContext.
In either case, when validation fails, the DataGridView seems to have lost the previous value for the cell.
If validation fails the user is prompted and the routine exits. I'd like for user to be able to hit the Esc key to return the previous value for the cell, but the cell remains empty.
Does anyone know why the previous value is no longer available when editing a cell's value programatically?
Here's what I'm doing so far. I am forcing the validation events to fire by calling the form's .Validate method. I don't know if I should be doing that or not. It is incomplete in that I am not yet handling new rows:
Private Sub PasteFromClipboard(ByVal sender As Object, ByVal e As KeyEventArgs)
Dim dgv = TryCast(sender, DataGridView)
If Not IsNothing(dgv) Then
If dgv.SelectedCells.Count > 0 Then
Dim rowSplitter = {ControlChars.Cr, ControlChars.Lf}
Dim columnSplitter = {ControlChars.Tab}
Dim topLeftCell = CopyPasteFunctions.GetTopLeftSelectedCell(dgv.SelectedCells)
If Not IsNothing(topLeftCell) Then
Dim data = Clipboard.GetData(DataFormats.Text)
If Not IsNothing(data) Then
Dim columnIndex = topLeftCell.ColumnIndex
Dim rowIndex = topLeftCell.RowIndex
Dim columnCount = dgv.Columns.Count
Dim rowCount = dgv.Rows.Count
'Split clipboard data into rows
Dim rows = data.ToString.Split(rowSplitter, StringSplitOptions.RemoveEmptyEntries)
For i = 0 To rows.Length - 1
'Split row into cell values
Dim values = rows(i).Split(columnSplitter)
For j = 0 To values.Length - 1
If (j <= (columnCount - 1)) AndAlso (i <= (rowCount - 1)) Then
Dim cell = dgv.Rows(rowIndex + i).Cells(columnIndex + j)
dgv.CurrentCell = cell
dgv.BeginEdit(False)
cell.Value = values(j)
If Not Me.Validate() Then
dgv.CancelEdit()
Exit Sub
Else
dgv.EndEdit()
End If
Else
Debug.Print(String.Format("RowIndex: {0}, ColumnIndex: {1}", i, j))
End If
Next
Next
End If
End If
End If
End If
End Sub
Public Module CopyPasteFunctions
Public Function GetTopLeftSelectedCell(ByVal cells As DataGridViewSelectedCellCollection) As DataGridViewCell
If Not IsNothing(cells) AndAlso cells.Count > 0 Then
Dim cellList = (From c In cells.Cast(Of DataGridViewCell)()
Order By c.RowIndex, c.ColumnIndex
Select c).ToList
Return cellList(0)
End If
Return Nothing
End Function
End Module
Thanks for any help on this. Hopefully others are working with EF5 and Winforms. If not, I'm on my own!

This gets the job done when the grid contains one column.
It is assumed that the developer is handling the CellValidating event correctly, meaning that if validation fails the event is cancelled.
This routine closely resembles the copy and paste behavior one observes in Excel.
Private Sub PasteFromClipboard(ByVal sender As Object, ByVal e As KeyEventArgs)
Dim dgv = TryCast(sender, DataGridView)
If Not IsNothing(dgv) AndAlso Clipboard.ContainsText Then
If dgv.SelectedCells.Count > 0 Then
Dim rowSplitter = {ControlChars.NewLine}
Dim columnSplitter = {ControlChars.Tab}
Dim topLeftCell = CopyPasteFunctions.GetTopLeftSelectedCell(dgv.SelectedCells)
If Not IsNothing(topLeftCell) Then
Dim clipBoardText = Clipboard.GetText(TextDataFormat.Text)
Dim columnIndex = topLeftCell.ColumnIndex
Dim rowIndex = topLeftCell.RowIndex
Dim columnCount = dgv.Columns.Count
Dim rows = clipBoardText.Split(rowSplitter, StringSplitOptions.None)
For i = 0 To rows.Length - 2
'Split row into cell values
Dim values = rows(i).Split(columnSplitter)
Dim rowCount = dgv.Rows.Count
For j = 0 To values.Length - 1
If (i <= (rowCount - 1)) AndAlso ((j + 1) <= columnCount) Then
Dim cell = dgv.Rows(rowIndex + i).Cells(columnIndex + j)
dgv.CurrentCell = cell
dgv.BeginEdit(False)
dgv.EditingControl.Text = values(j)
If Not Me.Validate() Then
Exit Sub
Else
dgv.EndEdit()
End If
End If
Next
Next
End If
End If
End If
End Sub
Public Module CopyPasteFunctions
Public Function GetTopLeftSelectedCell(ByVal cells As DataGridViewSelectedCellCollection) As DataGridViewCell
If Not IsNothing(cells) AndAlso cells.Count > 0 Then
Dim cellList = (From c In cells.Cast(Of DataGridViewCell)()
Order By c.RowIndex, c.ColumnIndex
Select c).ToList
Return cellList(0)
End If
Return Nothing
End Function
End Module

I would parse the data update the Entity Framework object with new objects of type that fits your Entity model. Then just save the Entity object and rebind your DGV.

Related

How to use function to prevent duplicated record for datagridview

I use function to prevent the same record goes into my datagridview but it doesnt work , when i separate the code out then its worked
i tried to seperate the for loop part out then the code work , but i wan to use function to do it so the code look more neater
Private Sub PicFavNote10_Click(sender As Object, e As EventArgs) Handles picFavNote10.Click
If validationDataGrid(lblNameNote10.Text) <> True Then
'if item didn added to the favorite data table yet
'add to favorite table
addTofavorite(lblUserLogin.Text, lblNameNote10.Text, lblDecpNote10.Text, txtPicNote10.Text, "SmartPhone", lblPriceNote10.Text)
End If
lblPriceNote10.Text = FormatCurrency(lblPriceNote10.Text)
End Sub
Private Function validationDataGrid(ByRef data As String) As Boolean
'validation on data grid view
For Each itm As DataGridViewRow In DGTFavTable.Rows 'loop though every item in datagrid
If itm.Cells(0).Value = data Then 'check wherter the text already exist
MsgBox(data & " Already added to your favorite cart")
Return True
Else
Return False
End If
Next
End Function
I expected the MsgBox(data & " Already added to your favorite cart") will excecute but instead the validationDataGrid function return false value even the item is already added to favorite datagridview
Before you loop all rows you need to call this sub as is an efficient workaround to validate new data on DataGridView:
Private Sub ForceGridValidation()
'Get the current cell
Dim currentCell As DataGridViewCell = DGTFavTable.CurrentCell
If currentCell IsNot Nothing Then
Dim colIndex As Integer = currentCell.ColumnIndex
If colIndex < DGTFavTable.Columns.Count - 1 Then
DGTFavTable.CurrentCell = DGTFavTable.Item(colIndex + 1, currentCell.RowIndex)
ElseIf colIndex > 1 Then
DGTFavTable.CurrentCell = DGTFavTable.Item(colIndex - 1, currentCell.RowIndex)
End If
'Set the original cell
DGTFavTable.CurrentCell = currentCell
End If
End Sub

How to compare DataGridView cell data to DataSet cell data

In a VB.NET WinForms project I'm working on in VS2013 I am detecting when the user changes something in a cell on the CellValueChanged event. When a row in the DataGridView is found that is different from the DataSet I highlight the row in pink.
In my code though, I only know how to iterate through all of the rows, rather than just compare the row that fired the CellValueChanged event to the DataSet.
Here's my current code:
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 edited row
CompareDgvToDataSource(e.RowIndex, e.ColumnIndex)
End Sub
Private Sub CompareDgvToDataSource(ByVal rowIndex As Integer, ByVal columnIndex As Integer)
' 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
Dim currentColor As System.Drawing.Color = dgvEmployees.AlternatingRowsDefaultCellStyle.BackColor
If Not row(i, DataRowVersion.Current).Equals(row(i, DataRowVersion.Original)) Then
Console.WriteLine("Row index: " & dt.Rows.IndexOf(row))
' This works
dgvEmployees.Rows(rowIndex).DefaultCellStyle.BackColor = Color.LightPink
Else
' Need to change the BackColor back to what it should be based on its original alternating row color
End If
Next
Next
Next
End If
End Sub
As you can see, I'm passing the row and column index of the changed cell, so how can I take that and compare it to the specific row and/or cell in the unchanged DataSet?
UPDATE
Nocturnal reminded me that I need to allow for sorting of the DGV, so using row indexes won't work. He offered this as a solution (changed slightly to work with my objects):
If Not dsChanged Is Nothing Then
For Each dtrow As DataRow In dsChanged.Tables("employees").Rows
If DirectCast(dtrow, EmployeesDataSet.employeesRow).employeeID.ToString = dgvEmployees.Rows(rowIndex).Cells("employeeID").Value.ToString Then
For i As Integer = 0 To dsChanged.Tables("employees").Columns.Count - 1
If Not dtrow(i, DataRowVersion.Current).Equals(dtrow(i, DataRowVersion.Original)) Then
Console.WriteLine("Employees ID: " & DirectCast(dtrow, EmployeesDataSet.employeesRow).employeeID)
dgvEmployees.Rows(rowIndex).Cells(columnIndex).Style.BackColor = Color.LightPink
Else
' Need to change the BackColor back to what it should be based on its original alternating row color
End If
Next
End If
Next
End If
However, I'm getting an error on the If DirectCast... line saying "Column named "employeeID" cannot be found." I'm not sure if the error is on the DataSet or on the DGV, but there is an employeeID column in the database and DataSet. There is an employeeID as a bound column for the DataGridView, but it is set to Visible = False. That's the only thing I can think of that could possibly cause that error, but if it's a bound column, I would think it could be compared against as in this case.
FINAL WORKING VERSION
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 edited row
CompareDgvToDataSource("employees", e.RowIndex, e.ColumnIndex)
End Sub
Private Sub CompareDgvToDataSource(ByVal dataSetName As String, ByVal rowIndex As Integer, ByVal columnIndex As Integer)
' Takes a dataset and the row and column indexes, checks if the row is different from the DataSet and colors the row appropriately
EmployeesBindingSource.EndEdit()
Dim dsChanges As DataSet = EmployeesDataSet.GetChanges()
If Not dsChanges Is Nothing Then
For Each dtrow As DataRow In dsChanges.Tables("employees").Rows
If DirectCast(dtrow, EmployeesDataSet.employeesRow).employeeID.ToString = dgvEmployees.Rows(rowIndex).Cells("employeeID").Value.ToString Then
For i As Integer = 0 To dsChanges.Tables("employees").Columns.Count - 1
If dtrow.RowState.ToString = DataRowState.Added.ToString Then
' TODO: Color entire new row
ElseIf dsChanges.Tables(dataSetName).Rows(0).HasVersion(DataRowVersion.Original) Then
If Not dtrow(i, DataRowVersion.Current).Equals(dtrow(i, DataRowVersion.Original)) Then
Console.WriteLine("Employees ID: " & DirectCast(dtrow, EmployeesDataSet.employeesRow).employeeID)
dgvEmployees.Rows(rowIndex).Cells(columnIndex).Style.BackColor = Color.LightPink
Else
' TODO: Need to change the BackColor back to what it should be based on its original alternating row color
End If
End If
Next
End If
Next
End If
End Sub
My project has four DGVs in it, so this will eventually be expanded to allow for all four DGVs to be checked for changes.
have a look at this approach it is simlar but only iterating the specific Table
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 edited row
CompareDgvToDataSource(e.RowIndex, e.ColumnIndex, sender.DataSource.datamember.ToString)
End Sub
and comparing the ID of the given Table
Private Sub CompareDgvToDataSource(ByVal rowIndex As Integer, ByVal columnIndex As Integer, tablename As String)
EmployeesBindingSource.EndEdit()
Dim dsChanged As DataSet = EmployeesDataSet.GetChanges()
If Not dsChanged Is Nothing Then
For Each dtrow As DataRow In dsChanged.Tables(tablename).Rows
If DirectCast(dtrow, EmployeesDataSet.EmployeeRow).EmployeeID = dgvEmployees.Rows(rowIndex).Cells("EmployeeID").Value Then
Dim currentColor As System.Drawing.Color = dgvEmployees.AlternatingRowsDefaultCellStyle.BackColor
For i As Integer = 0 To dsChanged.Tables(tablename).Columns.Count - 1
If Not dtrow(i, DataRowVersion.Current).Equals(dtrow(i, DataRowVersion.Original)) Then
Console.WriteLine("Employees ID: " & DirectCast(dtrow, EmployeesDataSet.EmployeeRow).EmployeeID)
' This works
dgvEmployees.Rows(rowIndex).Cells(columnIndex).Style.BackColor = Color.LightPink
Else
' Need to change the BackColor back to what it should be based on its original alternating row color
End If
Next
End If
Next
End If
End Sub
and i am only changing the backcolor of the cell that was changed not the entire row....but depends how you would use that information anyways
EDIT:
with
Console.WriteLine(EmployeesDataSet.Employee(rowIndex)(columnIndex, DataRowVersion.Original).ToString())
Console.WriteLine(EmployeesDataSet.Employee(rowIndex)(columnIndex, DataRowVersion.Current).ToString())
you can see Current and Original Values
123456

Copy data from C1FlexGrid to C1FlexGrid

I'm using a C1FlexGrid, which version I'm not entirely sure. I am trying to copy rows that are selected in FlexGridA to FlexGridB, and so far I'm unable to locate anything online to help, and all of the functionality that I've tried to use so far (such as add) does not work.
How can I accomplish this?
I HAVE to use C1FlexGrids, so if the answer is for a regular MSFlexGrid it won't work. I've already tried using some solutions for those and they don't work either.
EDIT:
I now have information copying between tables, but it's copying the entirety of what the table has after it's filtered, not just the selected rows.
There are two flexgrids. On form1 is fgResults, on form2 is fgDelete. I want to copy the selected rows from fgResults to fgDelete so that a user can see what they've selected to delete from the database before just blindly clicking and deleting something they don't want to.
Here is what I have to copy the data:
AddFilterRow(MultiDelete.fgDelete)
Dim MS As System.IO.MemoryStream = New System.IO.MemoryStream
fgResults.WriteXml(MS)
MS.Position = 0
MultiDelete.fgDelete.ReadXml(MS)
The issue is, again, it's adding EVERYTHING that was filtered, not just the selected rows.
So after several hours of digging, I found a workaround as there doesn't appear to be a direct way to copy one row from a C1FlexGrid to another.
Dim DT As New DataTable
For c = fgResults.Cols.Fixed To fgResults.Cols.Count - 1
DT.Columns.Add(fgResults.Cols(c).Name, fgResults.Cols(c).DataType)
Next
For r = fgResults.Rows.Fixed To fgResults.Rows.Count - 1
If fgResults.Rows(r).Selected Then
Dim row As DataRow = DT.NewRow()
For c = fgResults.Cols.Fixed To fgResults.Cols.Count - 1
If fgResults(r, c) Is Nothing Then
If fgResults.Cols(c).DataType.FullName = "System.DateTime" Then
row.Item(c - fgResults.Cols.Fixed) = Date.MinValue
ElseIf fgResults.Cols(c).DataType.FullName = "System.Double" Then
row.Item(c - fgResults.Cols.Fixed) = 0
Else
row.Item(c - fgResults.Cols.Fixed) = ""
End If
Else
row.Item(c - fgResults.Cols.Fixed) = fgResults(r, c)
End If
Next
DT.Rows.Add(row)
End If
Next
What I'm doing here is creating a new datatable, adding all rows to it that are selected, and then I fill the second grid with the created datatable. If anyone has a more direct way of doing this I am all ears, but this works for now for what I'm trying to accomplish.
i have the code to copy c1 flex grid cells to excel format
Dim fCol as integer
Dim fRow as integer
Private Sub Flex_EnterCell(ByVal sender As Object, ByVal e As System.EventArgs) Handles Flex.EnterCell
fRow = Flex.RowSel
fCol = Flex.ColSel
End Sub
Private Sub Copy_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Copy.Click
Dim lRow As Integer, nRow As Integer
Dim lCol As Integer, nCol As Integer
Dim nText As String
lRow = Flex.RowSel
lCol = Flex.ColSel
For nRow = fRow To lRow
For nCol = fCol To lCol
nText = nText & IIf(IsDBNull(Flex.Item(nRow, nCol)), Convert.ToChar(Keys.Tab), Flex.Item(nRow, nCol)) & Convert.ToChar(Keys.Tab)
Next
nText = nText & vbCrLf
Next
Clipboard.SetText(nText)
End Sub

Using single panel containing checkboxes for multiple treeview nodes (Images & Code Attached)

I am a VB.NET beginner.
I want to achieve following:
Node1 is clicked which opens a panel containing check-boxes.
The user will click a few check-boxes.
The user clicks node2 which will export check-box information to an Excel sheet column, and reset the panel.
New information entered on panel is exported to an adjacent column in the same Excel sheet used in step3.
The above process continues for 90 nodes.
How do I do the first part in steps 3, 4 and 5?
This is my first try which is not working:
Dim oXL As Excel.Application
Dim oWB As Excel.Workbook
Dim oSheet As Excel.Worksheet
oWB = oXL.Workbooks.Open("F:\open.xlsx")
oSheet = oWB.Worksheets("Sheet1")
'I am not able to think clearly on following loop
For i = 1 To 3
For j = 1 To 90
If CheckBox1.Checked Then
oSheet.Cells(i, j).value = "1"
Else : oSheet.Cells(i, j).value = "0"
End If
If CheckBox2.Checked Then
oSheet.Cells(i, j).value = "1"
Else : oSheet.Cells(i, j).value = "0"
End If
If CheckBox3.Checked Then
oSheet.Cells(i, j).value = "1"
Else : oSheet.Cells(i, j).value = "0"
End If
Next
Next
'Following works
CheckBox1.Checked() = False
CheckBox2.Checked() = False
CheckBox3.Checked() = False
ComboBox1.ResetText()
What you clearly need is a way to store the user's selections in memory, before saving them to the spreadsheet. There are several ways you could do this, but given your inexperience I suggest you consider the simplest, which is to define a basic class to represent the user's selections for a single node, and an array – where each item is an instance of the class – to store the entire set of user selections.
Define the node selection class – to represent selections for a single node:
Public Class NodeSelection
Public CheckA As Boolean
Public PickAIndex As Integer = -1
Public CheckB As Boolean
Public PickBIndex As Integer = -1
Public CheckC As Boolean
Public PickCIndex As Integer = -1
Public ItemProcessed As Boolean
End Class
Define your variables – in your form class (not in a sub):
Private _userPicks(89) As NodeSelection 'Array of user's selections
Private _previousIndex As Integer = -1 'Used to record the previously selected node
Instantiate the array items – in the form's Load event:
'Instantiate the class for each element of the array
For i As Integer = 0 To _userPicks.Count - 1
_userPicks(i) = New NodeSelection
Next
Keeping track of user selections:
Whenever a new node is selected, you need to update the array item for the previously selected node then reset the controls for the current node. This is best done in the treeview's AfterSelect event:
Private Sub TreeView1_AfterSelect(sender As Object, e As System.Windows.Forms.TreeViewEventArgs) Handles TreeView1.AfterSelect
'Exit if the click is on the parent node (Node0)
If e.Node.GetNodeCount(False) > 0 Then Return
UpdateNodeInfo()
'If the currently selected node has already been processed,
'restore user selection values to the controls,
'otherwise reset the controls
With _userPicks(e.Node.Index)
CheckBox1.Checked = If(.ItemProcessed, .CheckA, False)
ComboBox1.SelectedIndex = If(.ItemProcessed, .PickAIndex, -1)
CheckBox2.Checked = If(.ItemProcessed, .checkB, False)
ComboBox2.SelectedIndex = If(.ItemProcessed, .PickBIndex, -1)
CheckBox3.Checked = If(.ItemProcessed, .checkC, False)
ComboBox3.SelectedIndex = If(.ItemProcessed, .PickCIndex, -1)
End With
'Color the previous selection so the user can see it's been processed
If _previousIndex >= 0 Then TreeView1.Nodes(0).Nodes(_previousIndex).BackColor = Color.AntiqueWhite
'Record this (selected) node's index for updating when the next node is selected
_previousIndex = e.Node.Index
End Sub
Private Sub UpdateNodeInfo()
If _previousIndex < 0 Then Return 'No item has been set yet
With _userPicks(_previousIndex)
.CheckA = CheckBox1.Checked
.PickAIndex = If(.CheckA, ComboBox1.SelectedIndex, -1)
.CheckB = CheckBox2.Checked
.PickBIndex = If(.CheckB, ComboBox2.SelectedIndex, -1)
.checkC = CheckBox3.Checked
.PickCIndex = If(.checkC, ComboBox3.SelectedIndex, -1)
.ItemProcessed = True 'Record the fact the item has already been processed
End With
End Sub
Writing values to the spreadsheet:
Notice that I put the array item update routine in a separate procedure. This is because you will have to update the final selection before writing it all out to the spreadsheet. I presume you will have a button to save their selections, so you just need to call the UpdateNodeInfo sub from there before iterating over the array and writing the values. Here is how you might iterate over the values and update the spreadsheet:
For i As Integer = 0 To _userPicks.Count - 1
With _userPicks(i)
oSheet.Cells(3, i + 2) = "Node" & (i + 1).ToString
oSheet.Cells(9, i + 2) = "Node" & (i + 1).ToString
oSheet.Cells(4, i + 2) = If(.ItemProcessed AndAlso .CheckA, 1, 0)
oSheet.Cells(5, i + 2) = If(.ItemProcessed AndAlso .CheckB, 1, 0)
oSheet.Cells(6, i + 2) = If(.ItemProcessed AndAlso .checkC, 1, 0)
oSheet.Cells(10, i + 2) = If(.ItemProcessed AndAlso .CheckA, ComboBox1.Items(.PickAIndex).ToString, "")
oSheet.Cells(11, i + 2) = If(.ItemProcessed AndAlso .CheckB, ComboBox1.Items(.PickBIndex).ToString, "")
oSheet.Cells(12, i + 2) = If(.ItemProcessed AndAlso .checkC, ComboBox1.Items(.PickCIndex).ToString, "")
End With
Next
I assume you already know how to open, save and close the spreadsheet, so I'll leave that side of it up to you. Get familiar with the methods outlined here and post another question if you don't.
Finally
The above is a fairly simple way to achieve what you're trying to do. If you think you may need to add more functionality to your class, you should look at substituting public members for properties – some would say you should do that anyway – and you may want to consider storing the complete set of user selections in a List(Of T) object rather than an array.

Cannot remove rows in datagridview in vb.net

My datagridview is bound to a data table. I want to filter the datagridview without affecting data table. I know one way is to use bindingsource, but binding source will use column names in its filter. But I want to enable users to enter any string in a textbox to filter. So I pass datagridview by reference to a function PassFilter which removes unwanted rows in datagridview. I can see that it does execute rows removal in datagridview. But the problem is that when datagridview is shown on the form, it is unchanged, the same as the data table. I do not understand why rows are not removed on datagridview
The code is as follows:
DataGridView1.DataSource = table
PassFilter(DataGridView1, m_FilterQuick.strFilter.ToLower())
Private Sub PassFilter(ByRef datagridview As DataGridView, ByVal strFilter As String)
Dim i As Integer = 0
Dim j As Integer = 0
Dim containStr As Boolean = False
While i < datagridview.Rows.Count
j = 0
containStr = False
'If all cells in a row does not contain strFilter, delete this row from datagridview
While j < datagridview.Rows(i).Cells.Count
Dim c As DataGridViewCell = datagridview.Rows(i).Cells(j)
If Not c.Value Is DBNull.Value Or Nothing Then
If c.Value.ToString().ToLower().Contains(strFilter) Then
containStr = True
Exit While
End If
End If
j = j + 1
End While
If Not containStr Then
datagridview.Rows.RemoveAt(i)
End If
i = i + 1
End While
End Sub
Your big problem is that you're removing items in the DataGridViewRowCollection as you're iterating through that collection. Even if you're not getting an 'index out of range' error doing this you're going to end up removing the wrong row(s) as the index value of later rows change when you remove earlier rows.
What you should do is instead of removing rows in your row loop, add the row's index to a List(Of Integers).
After you're done iterating through all the rows in your DataGridView, Reverse() your list and use the row index values in your List to remove rows via the RemoveAt() method.
Here's what I mean:
Private Sub PassFilter(ByRef datagridview As DataGridView, ByVal strFilter As String)
Dim i As Integer = 0
Dim j As Integer = 0
Dim containStr As Boolean = False
' Row indexes we'll remove later on.
Dim deleteIndexList As List(Of Integer) = New List(Of Integer)
While i < datagridview.Rows.Count
j = 0
containStr = False
'If all cells in a row does not contain strFilter, delete this row from datagridview
While j < datagridview.Rows(i).Cells.Count
Dim c As DataGridViewCell = datagridview.Rows(i).Cells(j)
' Note: you'll want to enclose the tests for DBNull.Value and Nothing in parens like I show here.
If Not (c.Value Is DBNull.Value And c.Value Is Nothing) Then
If c.Value.ToString().ToLower().Contains(strFilter) Then
containStr = True
Exit While
End If
End If
j = j + 1
End While
If Not containStr Then
' Don't remove rows here or your row indexes will get out of whack!
' datagridview.Rows.RemoveAt(i)
deleteIndexList.Add(i)
End If
i = i + 1
End While
' Remove rows by reversed row index order (highest removed first) to keep the indexes in whack.
deleteIndexList.Reverse()
For Each idx As Integer In deleteIndexList
datagridview.Rows.RemoveAt(idx)
Next
End Sub
Are you displaying the data as read-only, or for edit?
If you're allowing the users to edit, can you find some way to hide the rows you don't want shown, rather than trying to remove them?
If you're displaying this as read-only, you could grab the data after the filtering, and throw it into a new datagridview.... (ugly, yes, but a possible option)
Oh wait... what about if you pass the datagridview by value instead of reference, so your changes at remove will be maintained?