DataGridView - Column Header Change Weirdness - vb.net

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.

Related

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.

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.

Determining ToolStripButton CheckState from ToolStrip loop

I have a VB.Net toolstrip to which I add buttons programmatically. Some of the buttons are checked or unchecked depending on the state they were left in when the user last set up the application (from values stored in the Registry:
Dim OneButton As New ToolStripButton("T", Nothing, Nothing, "Thailandr")
OneButton.CheckOnClick = True
AddHandler OneButton.Click, AddressOf ClickHandlerLayers
tsLayers.Items.Add(OneButton)
If GetSetting(IO.Path.GetFileNameWithoutExtension(Application.ExecutablePath), "Settings", "ThailandSetting", False) Then
OneButton.PerformClick()
End If
OneButton = New ToolStripButton("W", Nothing, Nothing, "World")
OneButton.CheckOnClick = True
AddHandler OneButton.Click, AddressOf ClickHandlerLayers
tsLayers.Items.Add(OneButton)
If GetSetting(IO.Path.GetFileNameWithoutExtension(Application.ExecutablePath), "Settings", "WorldSetting", False) Then
OneButton.PerformClick()
End If
Everything works fine except that I want to save the values of the buttons back into the Registry when the user clicks the Apply button. I want to save the values by looping through the tsLayers toolstrip rather than hard coding (which is possible, but is extra work when I add more buttons). So far I can see the names an
' Save which background layers are to be used
For Each tb As ToolStripItem In tsLayers.Items
Debug.Print(tb.Name)
Debug.Print(tb.GetType.ToString)
Debug.Print(tb.Selected)
Debug.Print(tb.Pressed)
Next
Results are:
Thailand
System.Windows.Forms.ToolStripButton
False
False
World
System.Windows.Forms.ToolStripButton
False
False
even if one of the buttons is pressed/checked at the time of looking at the results. I can't see any other properties that can help me, nor any collections that I can burrow into.
Is there a way to determine the checkState of a toolstripbutton in a toolstrip by looping through the toolstrip?
Cast the ToolStripItem as a ToolStripButton and you'll have access to the CheckState property
If tb.GetType Is GetType(ToolStripButton) Then
Dim tbCast As ToolStripButton = DirectCast(tb, ToolStripButton)
Debug.Print(tbCast.CheckState)
End If

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.

datagridview formatting not applying

In my form load event I am setting the defaultcellstyle formats. They are not taking hold anyone know why? None of the formatting that is expected in the code after I bind the datatable to the grid is getting done even though the code steps through it
Private Sub frmADRORD_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
'wire the delegate function for incoming updates
AddHandler m_helper.DataUpdate, AddressOf UpdateGridInvoker
'bind the visual grid with the binding source
Me.datagridADRORD.DataSource = dsGridView
'get data from helper class
m_dt = m_helper.GetTable()
'bind the binding source with datatable
Me.datagridADRORD.DataSource = m_dt
**'after data loaded, auto resize columns and format
Me.datagridADRORD.AutoResizeColumn(DataGridViewAutoSizeColumnMode.AllCellsExceptHeader)
With Me.datagridADRORD.ColumnHeadersDefaultCellStyle
.BackColor = Color.Gold
.Alignment = DataGridViewContentAlignment.MiddleCenter
.WrapMode = DataGridViewTriState.True
.Font = New Font(Control.DefaultFont, FontStyle.Bold)
End With
Me.datagridADRORD.Columns("ADR Price").DefaultCellStyle.Format = "##.##"
For Each row As DataGridViewRow In Me.datagridADRORD.Rows
row.Cells("ORD Price").DataGridView.DefaultCellStyle.FormatProvider = Globalization.CultureInfo.GetCultureInfo(m_helper.CurrCultureInfoStr(row.Cells("Currency").Value))
Next
Me.datagridADRORD.Columns("Currency Price").DefaultCellStyle.Format = "##.####"
Me.datagridADRORD.Columns("Difference").DefaultCellStyle.Format = "##.##"**
End Sub
You have to set the ValueType of the column, and "##.##" doesn't seem to work (in C# anyway).
This works in C#:
this.dataGridView1.Columns["Column3"].ValueType = typeof(Double);
this.dataGridView1.Columns["Column3"].DefaultCellStyle.Format = "N2";
Also the data being bound has to actually be of some numeric type (I presume it is, but can't be sure from your code snippet).
You'll have to translate typeof() to equivalent VB.NET, and I don't know whether the difference between "##.##" and "N2" is a control thing (in which case you'll need to change it) or a language thing (in which case you won't).