Updating Datagridview when datasource changes - vb.net

I have a Datagridview bound to a datatable. I'd like to hide or show a row depending on the value of a cell, for example if the cell value is "N" make the row visible else if it is "Y", hide the row.
The datacolumn is setup as:
New DataColumn With {.ColumnName = "Rec", .AllowDBNull = False, .DefaultValue = "N", .DataType = GetType(String)}
I have also handled the CellValueChanged event of the Datagridview as below:
Private Sub DataGridView1_CellValueChanged(sender As Object, e As DataGridViewCellEventArgs) Handles DataGridView1.CellValueChanged
If CType(sender, DataGridView).Columns(e.ColumnIndex).HeaderText.Equals("Rec") Then
CType(sender, DataGridView).Rows(e.RowIndex).Visible = CType(sender, DataGridView).Item(e.ColumnIndex, e.RowIndex).Value.Equals("N")
End If
End Sub
But when I programmatically change the value to "Y" (and I can see the value has changed in the grid), the row is still visible. I also put a break inside the event handler and it is NOT fired! So the question, how do I hide a datagridviewrow when I change a cellvalue to "Y"?
EDIT
Just to add some more clarity to the problem at hand, this is how I programmatically update the grid's datasource:
CType(DataGridView1.DataSource, DataTable).Item("Rec") = "Y"
I can directly update the grid and therefore fire the cellvaluechanged event, however this throws the CurrencyManager error, and therefore having to suspend binding then resuming binding to the grid every time the cell value updates. That works but at a snail's pace for even a small dataset (even with double buffering implemented via reflection).

Instead of binding directly to the DataTable, try binding the DataTable to a BindingSource (control) and then binding that to your grid. Then you can use the events in the BindingSource to check when the data has changed, either from a change in the grid or a programmatic change directly to the data table.
Alternatively, there are events fired by the DataTable that you can hook into to determine when the data has changed.

O.K when you update the cellvalue you extract the rowindex from the upgraded cell and in the next step you hide the cell with, some thing like:
DataGridView1.Rows.Item(rowindex).Visible = False
So for a test I did this:
DataGridView1(1, 2).Value = "Y"
DataGridView1.Rows.Item(2).Visible = False
and it works.

Right, I resolved this by partly using the suggestion by #John0987 as the issue I was facing seems to have been a bit more complex than I let on. Basically, it is a hack where I initialise up a form wide Boolean variable to false and handle the DataGridView's DataBindingComplete event which sets the Boolean to true and added a Boolean check in the CellValueChanged event before making the row invisible. This, so far, works flawlessly.

Related

How to set the CheckBox column in DataGridView value to true

GD All,
I've looking around for a solution to my below challenge.
I have got a form with an unbound datagridview, the dg has one added column that allows user to select a method to be used.
The state of the event is stored in a database and after re-opening the form the code checks if the event is in an 'open' state, if so it compares the previously selected method with the methods in the datagrid and should set the previously activated method to be the 'selected' method.
Yet I can't seem to get this to work unfortunately...
The below code loops through the methods in the dg and compares the values, if it meets the methodID it should set the value to 'True' or to the TrueValue anyway.
This is initialized if the database check returns true and after full initialisation of the form, where session.methodID is a field in the returned LINQ query.
For Each r As DataGridViewRow In dgMethods.Rows
If r.Cells(1).Value = session.methodID Then
Dim c As DataGridViewCheckBoxCell = r.Cells(0)
c.Value = c.TrueValue
End If
Next
Unfortunately, this doesn't set the checkbox to 'Checked'.
The loop runs and evaluates the comparison between r.Cells(1).Value and session.methodID correct and triggers correctly.
The interesting thing is if I do a similar loop after the 'CellContentClick' event it does do exactly what is expected. (the example below sets all checkbox values to checked)
Private Sub dgMethods_CellContentClick(sender As Object, e As DataGridViewCellEventArgs) Handles dgMethods.CellContentClick
'Only single selection allowed, so clear table before submitting new selection
For Each r As DataGridViewRow In dgMethods.Rows
Dim c As DataGridViewCheckBoxCell = r.Cells(0)
c.Value = c.TrueValue
Next
dgMethods.CommitEdit(DataGridViewDataErrorContexts.Commit)
End Sub
So, apparently there is a difference in the state between just calling the loop on the dgMethods and when the dgMethods.CellContentClick event has triggered, yet I do not know which one ?
There are many many post on trying to set the CheckBox column yet I have not been able to get any of them working.
Anybody have any idea ?
I would appreciate your suggestions ?
I was not sure of being undestand your question... but there's s simple way to check and change the state of a chechbox cell in a datagridview:
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
For Each dr As DataGridViewRow In DataGridView1.Rows
If CBool(dr.Cells(0).Value) = True Then dr.Cells(0).Value = False : Continue For
If CBool(dr.Cells(0).Value) = False Then dr.Cells(0).Value = True
Next
End Sub
In this example, when you click this button for each row in the datagridview, checks the checkboxcell and set the value to FALSE or TRUE depending of his value.
Hope this helps you.
And let me one more tip. If you get acces to the cells for his name instead of his index use his name, it should helps you avoiding troubles ;)
GD All,
After searching further I came across the following interesting behaviour.
The method selection process is part of a form called 'frmAddEvent', the frmAddEvent form is called from a main form using below routine.
The new form instance is created and afterwards filled using a public sub in the form class called InitializeForm() which uses a GUID parameter to retrieve corresponding data to set the form fields.
If Not (isOpened(rsTankName.unqID)) Then
Dim newForm As New frmAddEvent() '(rsTankName)
newForm.InitializeForm(rsTankName)
newForm.Show()
Else
End If
The initialization sub queries several datatables and sets the corresponding fields in the new form instance correctly if applicable.
Part of that setting is the method selection in the dgMethods datagridview.
It would appear that the sequence in which you call the form makes all the difference as the below code works perfectly:
If Not (isOpened(rsTankName.unqID)) Then
Dim newForm As New frmAddEvent() '(rsTankName)
newForm.Show()
newForm.InitializeForm(rsTankName)
Else
End If
So calling the newForm.InitializeForm(rsTankName)after the newForm.Show event allows the datagridview to set the CheckBoxColumn correctly.
Likely because the actual CheckBox itself is only actually generated upon the Show command, despite the fact that it is 'available' as a cell with DataGridViewCheckBoxColumn properties in the datagrid, directly after the New frmAddEvent has created the new form instance. The actual CheckBox and its corresponding CheckedState is not created before the newForm.Show event is called. It would appear that the when the CheckBox is created for display (during the newForm.Show event) there is no comparison made to its actual value.
So, in order to set the Checkbox column on initiating a new form you have to call the Show event prior to setting the DataGridViewCheckBoxColumn values otherwise the CheckBox will not show it as 'Checked'.

Monitoring Datagridview for rows that are being unhidden

I've got a a bit of code that is allowing a Results box to pop up, populating a DataGridView with information. The DataGridView uses the same BindingSource that another DataGridView uses, so when one is clicked, the other automatically moves to the same selected cell.
The issue I am having is the Result_DataGridView doesn't always contain all of the same rows as the master DGV, as it hides rows that don't match a criteria. If the user clicks on a cell on the Master DGV that isn't present in the Result DGV, the Result DGV un-hides that row (as you can't hide a selected row).
I'm currently trying to use this method to hide the row that appears again, but because of how VB treats "Entering a cell" it fires before the cell actually appears (as it fires on input focus, but before the cell actually appears in the DGV, so that row never gets checked)
Private Sub Result_Datagridview_CellEnter(sender As Object, e As DataGridViewCellEventArgs) Handles Result_Datagridview.CellEnter
Dim Result As Integer
Dim i
For row As Integer = 0 To Result_Datagridview.RowCount - 1
i = row
Result = Array.Find(ResultArray, Function(x) x = Result_Datagridview.Rows(i).Cells(0).Value)
If Result = 0 Then
Result_Datagridview.Rows(i).Visible = False
End If
Next
End Sub
If there was a ".RowsUnhidden" event, this would work fine.
Note - the ResultArray contains all of the index numbers that need to remain visible, the check is performed to see if any rows exist with an index number that does not appear in the array, if so, hide it again.
Does anyone have a work around or better approach to this?
Handling the RowEnter event, you can suspend the binding and reset row.Visible to False.
So, if ResultArray is an Integer array of visible row indices, you simply do the following:
Private Sub Result_Datagridview_RowEnter(sender As Object, e As DataGridViewCellEventArgs)
If Not ResultArray.Contains(e.RowIndex) Then
Dim currencyManager1 As CurrencyManager = DirectCast(BindingContext(Result_Datagridview.DataSource), CurrencyManager)
currencyManager1.SuspendBinding()
Result_Datagridview.Rows(e.RowIndex).Visible = False
currencyManager1.ResumeBinding()
End If
End Sub
You can look at the DataGridView.CellStateChanged event. It occurs when a cell get the focus, lose it, is selected, ...

DataGridView DataBindingComplete doesn't fire until after doing something in the form

In this form, there is a tab control. A new tab page is added for each table in the Data Set. Then to each tab page a Data Grid View is added for displaying the contents of the corresponding table.
This is the form's load event...You can see where I set the Data Source for the data grid view being added:
Public Sub dlgCreateTables_Load(sender As Object, e As System.EventArgs) Handles MyBase.Load
bindingComplete = False
For i = 0 To binData.BinnedTables.Tables.Count - 1
Dim tabPage As TabPage = New TabPage
tabPage.Text = binData.BinnedTables.Tables(i).TableName
tabTables.TabPages.Add(tabPage)
dgvData = New DataGridView
With dgvData
.Dock = DockStyle.Fill
.AllowUserToOrderColumns = False
.AllowUserToAddRows = False
.AllowUserToDeleteRows = False
.DataSource = binData.BinnedTables.Tables(i)
.Columns.Add("Total", "Total")
.Columns("Total").ReadOnly = True
End With
tabPage.Controls.Add(dgvData)
'These following lines have to go after dgvData is added to tabPage because otherwise they don't work
dgvData.AutoResizeColumnHeadersHeight()
For Each col As DataGridViewColumn In dgvData.Columns
col.SortMode = DataGridViewColumnSortMode.NotSortable
Next
'call handler for summing the columns for each row to make Total column once binding is complete
AddHandler dgvData.DataBindingComplete, AddressOf dgvData_Created
'And this to show row headers in the row selectors (but not the triangle when row is selected)
AddHandler dgvData.RowPostPaint, AddressOf MyDGV_RowPostPaint
AddHandler dgvData.KeyDown, AddressOf dgvData_KeyDown
AddHandler dgvData.CellValueChanged, AddressOf dgvData_CellEdit
Next 'DataTable In binData.BinnedTables.Tables
End Sub
For whatever reason, the DataBindingComplete event does not fire until after the form is fully loaded and I do something like click a button or change a value in the grid.
I have a different solution in which I do the same thing and it works in there. I can't for the life of me find anything different, or figure out why dgvData_Created isn't hit until something else happens.
Note that the contents of the tables in the Data Set to which the grids are bound are created via a separate module. Currently I'm just filling everything with a "2" to get this issue working.
UPDATE:
Per Saragis's suggestion, I moved the line to add the Handler for the DataBindingComplete event up to just after instantiating the DataGridView (before "With dgvData"). Now it works in that dgvData_Created does fire when adding dgvData to the tab page, and my table is there and looks great.
However, If there is > 1 table in my data set, the DataBindingComplete event only seems to fire for the first table. After the 1st grid is done, it goes back to adding the 2nd grid but skips DataBindingComplete, and goes to RowPostPaint after the form Load is complete.
As a result, the first grid's row headers are not painted until after I force a repaint (such as by going to a different tab then coming back). And any grids after that do not have the column header row sized right (though they do show the row headers).
How can I at least force a repaint programmatically for that first grid (since that is the one I open the form to)?? I tried .Refresh(), but that didn't work.

combobox recalling previous datasource

Looking for some insight here...
On my form, I have three ComboBox controls. When the left most receives a selection, it calls a routine to enable and populate the 2nd, then the same happens for the 3rd when a selection is made in the 2nd.
Private Sub cboEquipment_SelectedIndexChanged(sender As System.Object, e As System.EventArgs) Handles cboEquipment.SelectedIndexChanged
Me.transData.isMobile = If(Me.cboEquipment.Text = MOBIEQIP, 1, 0)
If cboMembership.Enabled = False Then
Me.cboMembership.Enabled = True
End If
Call loadGroups()
End Sub
The method 'loadGroups'
Private Sub loadGroups()
Dim cn As New SqlConnection(My.Settings.MyConnectionString)
Dim cm As New SqlCommand("SELECT ... ORDER BY codeHumanName ASC", cn)
Dim ta As New SqlDataAdapter(cm)
Dim dt As New DataTable
Me.cboMembership.DataSource = Nothing
Me.cboMembership.Items.Clear()
Me.transData.membership = Nothing
cn.Open()
ta.Fill(dt)
With Me.cboMembership
.DataSource = dt
.DisplayMember = "codeHumanName"
.ValueMember = "codeID"
End With
cn.Close()
End Sub
This works fine an dandy, everything loads at should, and provides the appropriate .ValueMember results.
I should mention that when the Form first loads, boxes 2 and 3 are empty and disabled. After their initial load, I have to select an item from the list to get values (as expected).
The odd part is that once the 2nd or 3rd Combobox have been 'initialized', they seem to recall last settings. For example, if I call the Reset method, which sets the .DataSource property to Nothing, clear the list, and set the Text to Nothing; the next time I change the SelectedIndex on cboEquipment, the other boxes come back to life with SelectedItems and SelectedValues.
These values aren't wrong according to the new DataTable, but it is odd that they remember at all (they shouldn't have any reference, and behave as a fresh load).
Other points of interest; the populate methods are called during the SelectedIndexChanged event.
Any thoughts would be great!
UPDATE -
This behavior is tied in with the SelectedIndexChange Event bouncing around (raise! raise! raise!)
Removing and adding event handlers at strategic points has my code working as I want/expect it to.
Here is the article that put me on the right path...
On your second and third combo boxes try clearing the .Text/.SelectedText properties before you set the .DataTable
The combo box is probably just being too helpful and assuming that when you set the .DataTable a second time, you'd want the selected item to match your .Text property.
I ended up using AddHandler and RemoveHandler to prevent chain-firing events as the comboboxes were being populated.

Select first visible cell of new row in DataGridView

I'm trying to focus input and fire the editing event on each new row that I add to a DataGridView in my form.
This is the code I am trying to user to achieve this.
Private Sub grd_GoldAdders_RowsAdded(ByVal sender As Object, ByVal e As System.Windows.Forms.DataGridViewRowsAddedEventArgs) Handles grd_GoldAdders.RowsAdded
Dim grid As DataGridView = DirectCast(sender, DataGridView)
grid.ClearSelection()
If grid.Rows(e.RowIndex).Cells("grid_flag").FormattedValue = Constants.[New] Then
For Each cell As DataGridViewCell In grid.Rows(e.RowIndex).Cells
If Not cell.Visible Then Continue For
grid.CurrentCell = cell
grid.BeginEdit(False)
Exit For
Next
End If
End Sub
The "grid_flag" is a hidden cell which is used to store custom states for a row.
Prior to adding a row, this is what we see on the form:
This is what we see when we actually try and add a new row:
Notice that both the column 0,0 and the first visible column of the new row are selected, but the column 0,0 has the focus. I do not wish for 0,0 to either get selected or have the focus. I also see here that the row indicator is pointing at row 0 too...
This is how I would like to see things after clicking my Add button:
Does anyone know where I am going wrong with the code? I've searched SO for the most part of the day trying to solve this one.
Instead of using your DataGridView's RowAdded event to set the CurrentCell, add the following code wherever you're adding a new record to your DGV (in your Add button's Click event I assume):
''# Add the new record to your Data source/DGV.
For Each row As DataGridViewRow In grd_GoldAdders.Rows
If row.Cells("grid_flag").FormattedValue = Constants.[New] Then
grd_GoldAdders.CurrentCell = row.Cells("AssySiteColumn") ''# I'm calling the first column in your DGV 'AssySiteColumn'.
grd_GoldAdders.BeginEdit(False)
Exit For
End If
Next
This code simply loops through all the rows in your DGV and specifies as the CurrentCell the first cell in the first row with your Constants.[New] flag value.