DataGridView DataBindingComplete doesn't fire until after doing something in the form - vb.net

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.

Related

Updating Datagridview when datasource changes

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.

DataGridView - Column Header Change Weirdness

So I currently have a form in whose Load event I add a dynamic number of TabPages and DataGridViews (DGV) - 1 DGV per TabPage. I want to handle certain events for these DGVs, so I also add handlers. This is the code in Load for that:
'Add a tab page and corresponding grid for each table in the data set
For i = 0 To binData.BinnedTables.Tables.Count - 1
Dim tabPage As C1.Win.C1Command.C1DockingTabPage = New C1.Win.C1Command.C1DockingTabPage
tabPage.Text = binData.BinnedTables.Tables(i).TableName
tabContent.TabPages.Add(tabPage)
dgvData = New DataGridView
AddHandler dgvData.DataBindingComplete, AddressOf dgvData_Created
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
col.ValueType = GetType(Integer)
Next
AddHandler dgvData.RowPostPaint, AddressOf MyDGV_RowPostPaint
AddHandler dgvData.KeyDown, AddressOf dgvData_KeyDown
AddHandler dgvData.CellValueChanged, AddressOf dgvData_CellChanged
AddHandler dgvData.CellEndEdit, AddressOf dgvData_CellEdit
AddHandler dgvData.CellValidating, AddressOf dgvData_CellValidating
AddHandler tabPage.TextChanged, AddressOf tabPage_TextChanged
AddHandler dgvData.ColumnHeaderMouseDoubleClick, AddressOf dgvData_ColumnHeaderMouseDoubleClick
AddHandler dgvData.ColumnHeaderCellChanged, AddressOf dgvData_ColumnHeaderTextChanged
Next 'DataTable In binData.BinnedTables.Tables
I use the ColumnHeaderMouseDoubleClick event to allow the user to change the text in the column header. I also want to update the column name for the corresponding column in the data source, so they are in sync. This is what I have in ColumnHeaderMouseDoubleClick:
Dim renameCol As New dlgNewCol
renameCol.lblInsertCol.Text = "Rename Column"
If renameCol.ShowDialog() = System.Windows.Forms.DialogResult.OK Then
Dim thisGrid As DataGridView = DirectCast(sender, DataGridView)
Dim thisCol As DataGridViewColumn = thisGrid.Columns(e.ColumnIndex)
thisCol.HeaderText = renameCol.txtColName.Text
'The index of the TabPage corresponds to the index of the table in the data source
binData.BinnedTables.Tables(tabContent.SelectedIndex).Columns(e.ColumnIndex - 1).ColumnName = thisCol.HeaderText
End If 'if dialog result is OK
What happens, though, is this works but then for whatever reason the column whose header was changed gets moved to the end of the grid, plus some other properties get reset.
If I comment out the line to update the column name of the table that is the data source for the DGV, then I don't have the issue with the column properties getting reset. However, then that source's column name does not get updated, so they are out of sync. Just having the table as the Data Source does not automatically update column names like it does the actual data.
I figured I'd use the ColumnHeaderCellChanged event to change these properties back to what they are supposed to be, because I'd expect that the code above would fire that....but it doesn't.
So I guess my questions are: Why does changing the column name of the data source have this effect (changing properties of the DGV's column)?, Why is the ColumnHeaderCellChanged event not firing?, And is there some other way I can manage to change both the DGV column's header and the data source's column name, plus have that column's other properties remain as they were (or be put back)?
Thank you!
Looks like AutoGenerateColumns is on. You'll need to turn it off and manual add the columns.

DataGridView bound to DataTable is not showing

I am trying to show a DataGridView in a form that is bound to a DataTable, but it's not showing up. I was doing this using a C1TrueDBGrid and that was working...I decided to switch to using a DataGridView due to some complications with the TrueDBGrid. Can anyone help me figure out why nothing is showing?
In the form I declare these:
Public binData As DataSet
Friend WithEvents dgvData As System.Windows.Forms.DataGridView
The binData is filled with tables created via a separate calculation routine. Then this is the form load event:
'create a tab page and add data grid view for every table in the set
For i = 0 To binData.Tables.Count - 1
Dim tabPage As C1.Win.C1Command.C1DockingTabPage = New C1.Win.C1Command.C1DockingTabPage
tabPage.Text = binData.Tables(i).TableName
tabContent.TabPages.Add(tabPage)
Dim dgvData = New System.Windows.Forms.DataGridView
Dim binding As New BindingSource
binding.DataSource = binData.Tables(i)
With dgvData
.Dock = DockStyle.Fill
.AllowUserToOrderColumns = False
.AllowUserToAddRows = False
.AllowUserToDeleteRows = False
.DefaultCellStyle.Alignment = DataGridViewContentAlignment.BottomLeft
.DataSource = binding
.AutoGenerateColumns = True
End With
tabPage.Controls.Add(dgvData)
Next 'DataTable In binData.Tables
When the form loads, the tab pages are there and labeled as expected, but they look empty (no table).
I did try instead setting the DataSource to the DataSet called binData (as opposed to a specific table), and then setting dgvData's DataMember property to the name of the specific table I want to display in it...that made no difference.
Note: I need to be able to do this programmatically at runtime as opposed to using the visual designer because I do not know the exact number of grids I need until the form loads with a particular dataset - the dataset it gets can have a different number of tables depending on what the user wants.
Here's some rough code to add dgvs to a flow panel:
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Static dgvCount As Integer
dgvCount += 1
Dim dgvNew As New DataGridView
dgvNew.Width = DataGridView1.Width
dgvNew.Height = DataGridView1.Height
dgvNew.Name = "dgv" & dgvCount
' clone other properties as need
FlowLayoutPanel1.Controls.Add(dgvNew)
Debug.Print(FlowLayoutPanel1.Controls(FlowLayoutPanel1.Controls.Count - 1).Name)
End Sub
Starts with one dgv - DataGridView1 as the model for properties. Anchoring is not working in the flow panel so some custom code may be need to change width.
Flow panel doesn't scroll so may not be the best choice - look into TableLayout as a possibility. TabControl is another option.
OK, well it turns out there was nothing wrong with what I was doing. The issue turned out to be in one line of code that has nothing to do with binding the DGV's datasource or anything.
ComponentOne has a control called a ThemeController in which you can set themes for your forms and the controls within. I had a line of code to set the theme for dgvData to my default application theme (which sets visual style and details regarding colors, fonts, etc.). For whatever reason, THAT was rendering my grid non-visible. I will be logging a ticket with them.

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.