controls linked together in FlowLayoutPanel - vb.net

I have a form that upon pressing a button creates a panel inside a FlowLayoutPanel. the panel does contain other control objects as can be seen from the pic.
The problem that controls in different panels are linked together ,when in choose in combobox all others change to the same
This before any selection
After selection :
Shouldn't they be bound by their parent control which in this case will be the panel ?

I just tested what happens when you bind two ComboBox controls to the same DataTable directly and when you bind two ComboBox controls to different BindingSources that are bound to the same DataTable. I added four ComboBoxes and two BindingSources to a form and then added this code:
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Dim table1 As New DataTable
Dim table2 As New DataTable
With table1.Columns
.Add("Id", GetType(Integer))
.Add("Name", GetType(String))
End With
With table2.Columns
.Add("Id", GetType(Integer))
.Add("Name", GetType(String))
End With
With table1.Rows
.Add(1, "One")
.Add(2, "Two")
End With
With table2.Rows
.Add(1, "First")
.Add(2, "Second")
End With
BindingSource1.DataSource = table1
BindingSource2.DataSource = table1
With ComboBox1
.DisplayMember = "Name"
.ValueMember = "Id"
.DataSource = BindingSource1
End With
With ComboBox2
.DisplayMember = "Name"
.ValueMember = "Id"
.DataSource = BindingSource2
End With
With ComboBox3
.DisplayMember = "Name"
.ValueMember = "Id"
.DataSource = table2
End With
With ComboBox4
.DisplayMember = "Name"
.ValueMember = "Id"
.DataSource = table2
End With
End Sub
When I ran the project, I saw "One" displayed in the first two ComboBoxes and "First" displayed in the last two, as expected. I was able to make a selection in either of the first two ComboBoxes without affecting the other, while making a selection in either of the last two ComboBoxes did affect the other. This is almost certainly a demonstration of the issue you're seeing and the solution to said issue. Using the same original data source is fine but you should pretty much always use a BindingSource to bind to your control(s).
If you create a user control, as I recommend you do for groups of child controls that you want to reuse, then the BindingSource object(s) can be added in the designer and used internally. You can just add a property to the control for the data source.

Related

datagridview values remove from another datagridview

I have two datagridviews. One is dtgridPopulate and is populated with data from the database.
dtgridPopulate columns are (checkbox, code, name)
checkbox
code
name
checkbox icon
c1
customer_one
Then the second datagridview is dtgridGenerate which has generated values from dtgridPopulate.
I use the code dtgridPopulate.Rows.Add(code, name) to add manually the value from dtgridPopulate to dtgridGenerate.
dtgridGenerate columns are (code, name)
code
name
c1
customer_one
When I check the checkbox in the dtgridPopulate it will transfer the values (code, name) to the dtgridGenerate. But the problem is when I uncheck the checkbox in the dtgridPopulate, It should also REMOVE the values in the dtgridGenerate.
Private Sub dtgridPopulateSelectAll_CurrentCellDirtyStateChanged(ByVal sender As Object, ByVal e As EventArgs) Handles dtgridPopulate.CurrentCellDirtyStateChanged
RemoveHandler dtgridPopulate.CurrentCellDirtyStateChanged, AddressOf dtgridPopulateSelectAll_CurrentCellDirtyStateChanged
If TypeOf dtgridPopulate.CurrentCell Is DataGridViewCheckBoxCell Then
dtgridPopulate.EndEdit()
Dim Checked As Boolean = CType(dtgridPopulate.CurrentCell.Value, Boolean)
If Checked Then
code = dtgridPopulate.CurrentRow.Cells(1).Value.ToString
name = dtgridPopulate.CurrentRow.Cells(2).Value.ToString
dtgridGenerate.Rows.Add(code, name)
Else
For Each drow As DataGridViewRow In dtgridPopulate.SelectedRows 'This is for uncheck but it doens't work
dtgridGenerate.Rows.Remove(row)
Next
End If
End If
AddHandler dtgridPopulate.CurrentCellDirtyStateChanged, AddressOf dtgridPopulateSelectAll_CurrentCellDirtyStateChanged
End Sub
I refer to this reference
Error when I uncheck the checkbox: row provided does not belong to this datagridview control. parameter name: datagridviewrow
I am not sure “why” you have unsubscribed and then re-subscribed to the event. Typically, this is only needed if the code in the event “changes” something in the grid that would cause the event to re-fire and this is not happening in your current code. Nor is ending the grids edit necessary.
Also, your use of the grids CurrentCellDirtyStateChanged event has one drawback in relation to what you want to do… it will NOT re-fire if the user checks and unchecks the same cell over and over. In other words, if the user clicks a check box cell and the check box becomes checked and the event fires… then … if the user “unchecks” the same cell before clicking on any other cell, then the event will NOT refire.
Also, it looks like the event is using a different grid from the signature of the event… dtgridPopulateSelectAll_CurrentCellDirtyStateChanged … ? … “SelectAll” and the grid in the event is called… dtgridPopulate … this is confusing.
Given this, I suggest two things to help. 1) create and use an “Empty” DataTable for the second grid. This way we could simply add and remove items from that table. 2) use the grids CellContnetClick event for this. It will re-fire if the user clicks on the same cell over and over in addition to firing ONLY when the check box is changed.
So, I propose these small changes. First add a global variable named generateDT as a DataTable used as a DataSource to the second grid. Initially this table would be empty. Second use the grids CellContentClick event to make these additions and removal of the rows.
Dim generateDT As DataTable
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Dim dt As DataTable = New DataTable()
dt.Columns.Add("checkbox", GetType(Boolean))
dt.Columns.Add("code", GetType(String))
dt.Columns.Add("Name", GetType(String))
dt.Rows.Add(False, "c1", "customer 1")
dt.Rows.Add(False, "c2", "customer 2")
dt.Rows.Add(False, "c3", "customer 3")
dtgridPopulate.DataSource = dt
generateDT = New DataTable()
generateDT.Columns.Add("code", GetType(String))
generateDT.Columns.Add("Name", GetType(String))
dtgridGenerate.DataSource = generateDT
End Sub
Then in the cell content click event, if the clicked cell is a check box cell, then… grab the checked state and both the code and name of the clicked-on row. Then if the check box cell is checked, then simply add the code and name to the second grids global DataTable variable generateDT. If the check box is unchecked, then we will loop through each row in the second grid and if we find a match then simply remove that row from the second grids global DataTable… something like…
Private Sub dtgridPopulate_CellContentClick(sender As Object, e As DataGridViewCellEventArgs) Handles dtgridPopulate.CellContentClick
If e.ColumnIndex = 0 Then
Dim Checked = CType(dtgridPopulate.Rows(e.RowIndex).Cells(e.ColumnIndex).EditedFormattedValue, Boolean)
Dim code = dtgridPopulate.CurrentRow.Cells(1).Value.ToString
Dim Name = dtgridPopulate.CurrentRow.Cells(2).Value.ToString
If Checked Then
generateDT.Rows.Add(code, Name)
Else
Dim rowToRemove As DataRowView
For Each row As DataGridViewRow In dtgridGenerate.Rows
If row.Cells(0).Value.ToString() = code Then
rowToRemove = CType(row.DataBoundItem, DataRowView)
generateDT.Rows.Remove(rowToRemove.Row)
Exit For
End If
Next
End If
End If
End Sub

Fill a ComboBox with a DataSet

I'm trying to use a DataSet, filled from a SQL Database, that have only one column with unique names to fill ComboBoxes with.
Right now I'm using this code:
ClassTables.FillDistrib()
ComboBox.DataSource = ClassTables.Distrib.Tables("Names")
ClassTables is a Class used to fill my DataSets.
Distrib is the name of my DataSet (FillDistrib is the Sub used to clear and fill it)
ComboBox is the name of my ComboBox
But the ComboBox droplist is left blank.
However, used in a DataGridView, it appears that the DataSet is filled correctly.
When assigning a complex object as a DataTable to the DataSource property of ComboBox or ListBox controls, specify, using the DisplayMember property, which Column of the DataTable should be used as source to display the text of the ListControl Items.
If the selected item should also return a value different from the text displayed, also set the ValueMember property to the name of the Column that provides the associated values.
Possibly, before setting the Control's DataSource reference (to avoid redundant iterations of the underlying data).
Let's build a DataTable to test the procedure:
Assign a DataTable to the DataSource of a ComboBox control, specifying as the DisplayMember the name of the Column that provides the text to display and as ValueMember the name of the Column that provides additional data that will be returned by the ComboBox.SelectedValue property when a user changes the SelectedItem:
Dim dt As New DataTable("TestTable")
dt.Columns.AddRange({
New DataColumn("Names", GetType(String)),
New DataColumn("Values", GetType(Integer))
})
Dim row As DataRow = dt.NewRow()
dt.Rows.Add({"Some Name", 1})
dt.Rows.Add({"Some OtherName Jr.", 2})
dt.Rows.Add({"Another Name", 3})
dt.Rows.Add({"Last Name", 4})
ComboBox1.DisplayMember = "Names"
ComboBox1.ValueMember = "Values"
ComboBox1.DataSource = dt
Private Sub ComboBox1_SelectionChangeCommitted(sender As Object, e As EventArgs) Handles ComboBox1.SelectionChangeCommitted
Dim cbo = DirectCast(sender, ComboBox)
TextBox1.Text = cbo.GetItemText(cbo.SelectedValue)
End Sub
Result:

DataGridViewComboBoxCell won't drop

I'm using vb.NET in Visual Studio 2010. I found an example of how to add a ComboBox to a DataGridView cell, and I added it to my code. When I run the code, and I add a new row, the ComboBox is visible, but it has no value displayed in it and it won't drop down.
Have I missed something from the code? Does the DataGridView need to have certain properties set?
dgvFiles.Rows.Add({"Cell1","Cell2"})
Dim gridComboBox As New DataGridViewComboBoxCell
gridComboBox.Items.Add("A") 'Populate the Combobox
gridComboBox.Items.Add("B") 'Populate the Combobox
gridComboBox.Items.Add("C") 'Populate the Combobox
dgvFiles(2, dgvFiles.Rows.Count - 1) = gridComboBox
Edit:
I had set four columns at design time, that wasn't the issue. The issue turned out to be that I had set the DataGridView to 'EditProgrammatically'. I had changed it to that initially to stop users from editing the text cells, but apparently, it prevented the ComboBoxes from dropping.
I appreciate all the answers given. My apologies that I forgot to mention that I had set four columns in design time, and that this issue was caused by me not realising the EditProgrammatically setting had this effect.
Your code is almost fine. Everything drops down. You could have the default value displayed on your list.
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
dgvfiles.Columns.Add("Column1", "Column 1")
dgvfiles.Columns.Add("Column2", "Column 2")
dgvfiles.Columns.Add("Column3", "Column 3")
dgvfiles.Columns.Add("Column4", "Column 4")
dgvfiles.Rows.Add({"Cell1", "Cell2"})
Dim gridComboBox As New DataGridViewComboBoxCell
gridComboBox.Items.Add("A") 'Populate the Combobox
gridComboBox.Items.Add("B") 'Populate the Combobox
gridComboBox.Items.Add("C") 'Populate the Combobox
gridComboBox.Value = gridComboBox.Items(0)
dgvfiles(2, dgvfiles.Rows.Count - 2) = gridComboBox
End Sub
Private Sub dgvfiles_CellBeginEdit(sender As Object, e As DataGridViewCellCancelEventArgs) Handles dgvfiles.CellBeginEdit
If e.ColumnIndex = 2 Then
'Do something
Else
e.Cancel = True
End If
End Sub

Databinding one control per row in the same datasource

I've been trying to deal with a frustrating issue with regards to databinding in Winforms.
I have a data source, which is a DataTable in a DataSet. This DataTable has three rows. I have three CheckBox controls on my form, and a Button. I want to bind each CheckBox to one row in this data source, and have the data source reflect the value in the Checked property whenever the CheckBox is updated. I also want these changes to be correctly picked up by calls to HasChanges() and calls to GetChanges().
When the Button is clicked, EndCurrentEdit() is called and passed the data-source that was bound to, and the DataSet is checked for changes using the HasChanges() method.
However, in my attempts to do this, I encounter one of two scenarios after the call to EndCurrentEdit().
In the first scenario, only the first CheckBox has its changes detected. In the second scenario, all other CheckBoxes are updated to the value of the CheckBox that was last checked on the call to EndCurrentEdit().
In looking at the RowState values after the call to EndCurrentEdit(), in Scenario 1, only the first row ever has a state of Modified. In Scenario 2, only the third row ever has a state of Modified. For Scenario 2, it doesn't matter whether the user updated the third CheckBox or not.
To demonstrate my problem, I have created a simple example which demonstrates it.
It's a stock Windows form containing three CheckBox controls and a Button control, all with their default names.
Option Strict On
Option Explicit On
Public Class Form1
Public ds As DataSet
Public Sub New()
' This call is required by the designer.
InitializeComponent()
' Add any initialization after the InitializeComponent() call.
ds = New DataSet()
Dim dt As New DataTable()
dt.Columns.Add("ID", GetType(Integer))
dt.Columns.Add("Selected", GetType(Boolean))
dt.Rows.Add(1, False)
dt.Rows.Add(2, False)
dt.Rows.Add(3, False)
dt.TableName = "Table1"
ds.Tables.Add(dt)
ds.AcceptChanges()
For i As Integer = 1 To 3
Dim bs As New BindingSource()
'After the call to Me.BindingContext(ds.Tables("Table1")).EndCurrentEdit() there are two scenarios:
'Scenario 1 - only changes to the first CheckBox are detected.
'Scenario 2 - when any CheckBox is checked, they all become checked.
'Uncomment the first and comment out the second to see Scenario 1.
'Uncomment the second and comment out the first to see Scenario 2.
'bs.DataSource = New DataView(ds.Tables("Table1")) 'Scenario 1
bs.DataSource = ds.Tables("Table1") 'Scenario 2
bs.Filter = "ID=" & i
Dim db As New Binding("Checked", bs, "Selected")
db.DataSourceUpdateMode = DataSourceUpdateMode.OnPropertyChanged
If i = 1
CheckBox1.DataBindings.Add(db)
ElseIf i = 2
CheckBox2.DataBindings.Add(db)
ElseIf i = 3
CheckBox3.DataBindings.Add(db)
End If
Next
End Sub
Private Sub Button1_Click( sender As Object, e As EventArgs) Handles Button1.Click
Me.BindingContext(ds.Tables("Table1")).EndCurrentEdit()
If ds.HasChanges()
MessageBox.Show("Number of rows changed: " & ds.GetChanges().Tables("Table1").Rows.Count)
ds.AcceptChanges()
End If
End Sub
End Class
I've done a lot of searching but haven't been able to work out what's happening, and am thus at a complete loss. It feels like what I'm trying to do is pretty simple, but I suspect that I must have misunderstood something somewhere or missed something important with regards to the binding process.
EDIT
It's already in the second paragraph, but just to make it clear, here is the basic outline of what I'm trying to do:
I have a DataSet containing a DataTable with values. For this example, there's two columns. ID could be any Integer, 'Selected' could be any Boolean value. None of these will ever be Nothing or DBNull.
I want to bind each row to ONE Checkbox control. One CheckBox per ID value. The Checked property should be bound to the Selected column in the DataTable.
When changes are made, and the user clicks the Button, I want to be able to tell what changes the user made, using the HasChanges() and GetChanges() methods of the DataSet (ie 2 rows updated if the user has changed the Checked property of two of the Checkbox controls).
EDIT 2
Thanks to #RezaAghaei I have come up with a solution. I have refined the code from my example of the problem, and made all controls generate based on the data (and position themselves accordingly) to make this example simple to copy-and-paste. Also, this uses a RowFilter on the DataView, rather than the Position property of the BindingSource.
Option Strict On
Option Explicit On
Public Class Form1
Public ds As DataSet
Private Sub Form1_Load( sender As Object, e As EventArgs) Handles MyBase.Load
ds = New DataSet()
Dim dt As New DataTable()
dt.Columns.Add("ID", GetType(Integer))
dt.Columns.Add("Selected", GetType(Boolean))
dt.Rows.Add(1, False)
dt.Rows.Add(2, False)
dt.Rows.Add(3, False)
dt.TableName = "Table1"
ds.Tables.Add(dt)
ds.AcceptChanges()
AddHandler dt.ColumnChanged, Sub(sender2 As Object, e2 As DataColumnChangeEventArgs)
e2.Row.EndEdit()
End Sub
For i As Integer = 0 To dt.Rows.Count-1
Dim cb As New CheckBox() With {.Text = "CheckBox" & i+1, .Location = New Point(10, 25 * (i))}
Dim dv As New DataView(dt)
dv.RowFilter = "ID=" & DirectCast(dt.Rows(i)(0), Integer)
Dim bs As New BindingSource(dv, Nothing)
Dim db As New Binding("Checked", bs, "Selected")
db.DataSourceUpdateMode = DataSourceUpdateMode.OnPropertyChanged
cb.DataBindings.Add(db)
Me.Controls.Add(cb)
Next
Dim btn As New Button()
btn.Location = New Point(10, 30 * dt.Rows.Count)
btn.Text = "Submit"
AddHandler btn.Click, AddressOf Button1_Click
Me.Controls.Add(btn)
End Sub
Private Sub Button1_Click( sender As Object, e As EventArgs)
'Me.BindingContext(ds.Tables("Table1")).EndCurrentEdit() 'Doesn't cut the mustard!
If ds.HasChanges()
MessageBox.Show("Number of rows changed: " & ds.GetChanges().Tables("Table1").Rows.Count)
ds.AcceptChanges()
Else
MessageBox.Show("Number of rows changed: 0")
End If
End Sub
End Class
The key is the call to EndEdit() in the ColumnChanged event for DataTable (a general call to EndCurrentEdit() just doesn't appear to cut it), although one additional problem I encountered was that this code won't work if it's in the form's New() method, even if it's after the call to InitializeComponent(). I'm guessing this is because there's some initialisation that Winforms does after the call to New() which is necessary for data binding to work properly.
I hope this example saves others the time I spent looking into this.
Consider these corrections and the problem will be solved:
To bind a control to an specific index of a list, bind control to a binding source containing the list, and then set the Position of binding source to the specifixindex.
When adding data-binding to CheckBox controls, set update mode to OnPropertyChanged.
Handle CheckedChanged event of CheckBox controls and call Invalidate method of grid to show changes in grid.
In CheckedChanged also call EndEdit of the BindingSource which the CheckBox is bound to.
Note: Call EndEdit usnig BeginInvoke to let all processes of checking the checkbox (including updating data source) be completed.
C# Example
DataTable dt = new DataTable();
private void Form3_Load(object sender, EventArgs e)
{
dt.Columns.Add("Id");
dt.Columns.Add("Selected", typeof(bool));
dt.Rows.Add("1", true);
dt.Rows.Add("2", false);
dt.Rows.Add("3", true);
this.dataGridView1.DataSource = dt;
var chekBoxes = new CheckBox[] { checkBox1, checkBox2, checkBox3 };
for (int i = 0; i < dt.Rows.Count; i++)
{
var bs = new BindingSource(dt, null);
chekBoxes[i].DataBindings.Add("Checked", bs, "Selected",
true, DataSourceUpdateMode.OnPropertyChanged);
chekBoxes[i].CheckedChanged += (obj, arg) =>
{
this.dataGridView1.Invalidate();
var c = (CheckBox)obj;
var b = (BindingSource)(c.DataBindings[0].DataSource);
this.BeginInvoke(()=>{b.EndEdit();});
};
bs.Position = i;
}
}
VB Example
Dim dt As DataTable = New DataTable()
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) _
Handles MyBase.Load
dt.Columns.Add("Id")
dt.Columns.Add("Selected", GetType(Boolean))
dt.Rows.Add("1", True)
dt.Rows.Add("2", False)
dt.Rows.Add("3", True)
dt.AcceptChanges()
Me.DataGridView1.DataSource = dt
Dim chekBoxes = New CheckBox() {CheckBox1, CheckBox2, CheckBox3}
For i = 0 To dt.Rows.Count - 1
Dim bs = New BindingSource(dt, Nothing)
chekBoxes(i).DataBindings.Add("Checked", bs, "Selected", _
True, DataSourceUpdateMode.OnPropertyChanged)
AddHandler chekBoxes(i).CheckedChanged, _
Sub(obj, arg)
Me.DataGridView1.Invalidate()
Dim c = DirectCast(obj, CheckBox)
Dim b = DirectCast(c.DataBindings(0).DataSource, BindingSource)
Me.BeginInvoke(Sub() b.EndEdit())
End Sub
bs.Position = i
Next i
End Sub

Programatically setting CheckBox value in DataGridView on another Tab does not commit

I have a form with two tabs, each with a DataGridView.
DGV1 on Tab1 is bound to a DataTable on form load.
DGV2 on Tab2 is not bound on form load, but includes a CheckBoxColumn to be utilized later.
When I select a row in DGV1, DGV2 is populated and I set each checkbox on DGV2 to True.
However, after a cell in DGV1 is clicked and DGV2 is subsequently populated and the checkboxes are set to True, they are reverted to Nothing when I first go to Tab2. However, once I have "seen" DGV2 by clicking the second tab, I can go back to DGV1 and click the same row, and now the checkboxes will actually be checked.
I have tried using Refresh and RefreshEdit to no avail.
Public Class Form1
Private ConnectionString As String = "..."
Private Function GetDataTable(selectString) As DataTable
Dim adapter As New SqlDataAdapter(selectString, ConnectionString)
Dim dt As New DataTable
Dim bs As New BindingSource
adapter.Fill(dt)
bs.DataSource = dt
Return dt
End Function
Private Sub Form1_Load(...) Handles Me.Load
DGV1.DataSource = GetDataTable("SELECT * FROM ...")
End Sub
Private Sub DGV1_CellClick(...) Handles DGV1.CellClick
DGV2.DataSource = GetDataTable("SELECT * FROM ...")
For i = 0 To dt.Rows.Count - 1
DGV2.Item(0, i).value = True
Next
End Sub
End Class
This seems to only apply to the CheckBoxColumn, as I can programatically edit other non-checkbox cells in the same manner and the changes are committed correctly. Any suggestions?
The best solution I could come up with was to add a boolean column to the DataTable before setting it as the BindingSource, rather than adding the CheckBoxColumn to the DataGridView.
dt.Columns.Add("Selected", GetType(System.Boolean)).SetOrdinal(0)
Admittedly, I cannot figure out why this works as I am still programatically setting the values of the CheckBoxColumn of the DataGridView.