How to change the data source of a ComboBox? - vb.net

I have two comboboxes. The data sorce of combobox1 is a list of string that is fixed. Combobox2's data sorce will be a list of string that depends on the selection of combobox1. This is very similar to a common case as follows: first enter your contry, then depending on your enter, the second combobox will show you the list of universities in the country.
'Change selection of first combobox
Private Sub cbxClient_SelectedIndexChanged(sender As Object, e As EventArgs) Handles cbxClient.SelectedIndexChanged
Try
If cbxClient.SelectedIndex <> -1 Then
GetAccount()
End If
Catch
Throw
End Try
End Sub
'Based on selection of first combobox, update the data sorce of second combobox
Private Sub GetAccount()
Try
m_listAccount.Clear()
Dim strClient As String = cbxClient.SelectedItem.ToString
For i As Integer = 0 To m_listDS.Count - 1
If m_listDS(i).Client.Tostring = strClient Then
m_ds = m_listDS(i)
Exit For
End If
Next
If Not m_ds Is Nothing Then
For Each row As DataRow In m_ds.Tables("Account").Rows
m_listAccount.Add(row("account").ToString)
Next
End If
cbxAccount.DataSource = m_listAccount
Catch ex As Exception
End Try
End Sub
My problem is that though m_listAccount updates (has the correct information), the choices shown in cbxAccount are not updated according to m_listAccount (has the wrong information). I do not understand why this happens.
Note: Assume that old string in m_listAccount is {"old1"} (the list only has 1 string) and after updating, the string in m_listAccount is {"new"}. Through break points, I get the following:
cbxAccount.DataSorce={"new"}
cbxAccount.SelectedItem={"old1"}
In the form, cbxAccount shows "old1" string.

Try this,
cbxAccount.DataSource = Nothing
cbxAccount.DataSource = m_listAccount
If you are just updating the same list object, the datasource might not automatically update, but should if the pointer changes to the datasource.
Or you could try,
m_listAccount.AcceptChanges()
If m_listAccount is a DataTable

Doesn't look like you're actually calling the databind method, i.e.
cbxAccount.DataBind()
Try putting that just after you set the data source.

Related

datagridview last programmatically changed cell does not get included in changedrows

VB.net app changes values in a datagridview programmatically. The values are all as they should be, but the save routine (dtShipments is the datatable that is the source for the datagridview)
Dim dtChanges As DataTable = dtShipments.getchanges()
If more than one has changed, dtChanges is always missing the last row.
In the routine that changes the cell values, I have tried DatagridView1.EndEdit and DatagridView1.CommitEdit, but the behavior is the same. I even tried adding a SendKeys.Send(vbTab) line, since hitting the tab key when making the changes manually is enough to get all the changes to show up in .GetChanges.
What am I missing?
code per request:
Private Sub btnAssign_Click(sender As Object, e As EventArgs) Handles btnAssign.Click
strModErr = "Form1_btnAssign_Click"
Dim sTruck As String = ""
Try
sTruck = Me.DataGridView2.SelectedCells(0).Value.ToString
For Each row As DataGridViewRow In DataGridView1.SelectedRows
row.Cells("Truck").Value = sTruck
Next
DataGridView1.CommitEdit(DataGridViewDataErrorContexts.Commit)
Catch ex As Exception
WriteErrorToLog(Err.Number, strModErr + " - " + Err.Description)
End Try
End Sub
Private Function SaveChanges() As Boolean
strModErr = "Form1_SaveChanges"
Dim Conn As New SqlConnection(My.Settings.SQLConnectionString)
Dim sSQL As String = "UPDATE fs_Shipments SET Truck = #Truck, Stop = #Stop WHERE SalesOrder = #SO"
Dim cmd As New SqlCommand(sSQL, Conn)
Dim sSO, sTruck As String
Dim iStop As Integer = 0
Try
DataGridView1.EndEdit()
DataGridView1.ClearSelection()
Dim dtChanges As DataTable = dtShipments.getchanges() 'DataRowState.Modified
If Not dtChanges Is Nothing Then
Conn.Open()
For Each row As DataRow In dtChanges.Rows
sSO = row("SalesOrder").ToString
sTruck = row("Truck").ToString
iStop = CInt(row("Stop").ToString)
With cmd.Parameters
.Clear()
.AddWithValue("#SO", sSO)
.AddWithValue("#Truck", sTruck)
.AddWithValue("#Stop", iStop)
End With
cmd.ExecuteNonQuery()
Next
End If
Return True
Catch ex As Exception
WriteErrorToLog(Err.Number, strModErr + " - " + Err.Description)
Return False
End Try
End Function
I am not exactly 100% sure why this happens. The problem appears to be specific to when the user “selects” the cells in the first grid, and then calls the SaveChanges code “BEFORE” the user has made another selection in the first grid. In other words, if the user “selects” the rows to “assign” the truck to in grid 1, then, “AFTER” the “selected” cells have been filled with the selected truck, THEN, the user selects some other cell in grid 1, THEN calls the save changes code. In that case the code works as expected.
All my attempts at committing the changes, either failed or introduced other issues. I am confident a lot of this has to do with the grids SelectedRows collection. IMHO, this looks like a risky way to set the cell values, I would think a more user-friendly approach may be to add a check box on each row and assign the truck values to the rows that are checked. But this is an opinion.
Anyway, after multiple attempts, the solution I came up with only further demonstrates why using a BindingSource is useful in many ways. In this case, it appears the DataTable is not getting updated with the last cell change. Again, I am not sure “why” this is, however since it appears to work using a BindingSource, I can only assume it has something to do with the DataTable itself. In other words, before the “Save” code is executed, we could call the tables AcceptChanges method, but then we would lose those changes. So that is not an option.
To help, below is a full (no-fluff) example. In the future, I highly recommend you pull out the parts of the code that do NOT pertain to the question. Example, all the code that saves the changes to the DB is superfluous in relation to the question… so remove it. The more unnecessary code you add to your question only increases the number of SO users that will “ignore” the question. If you post minimal, complete and working code that reproduces the problem so that users can simply copy and paste without having to add addition code or remove unnecessary code will increase you chances of getting a good answer. Just a heads up for the future.
The solution below simply adds a BindingSource. The BindingSource’s DataSource is the DataTable dtShipments then the BindingSource is used a DataSource to DataGridView1. Then in the SaveChanges method, “before” the code calls the dtShipment.GetChanges() method, we will call the BindingSources.ResetBinding() method which should complete the edits to the underlying data source, in this case the DataTable dtShipments.
If you create a new VS-VB winforms solution, drop two (2) grids and two (2) buttons onto the form as shown below, then change the button names to “btnAssign” and “btnApplyChanges.” The assign button will add the selected truck in grid two to the selected rows in grid one. The apply changes button simply resets the binding source and displays a message box with the number of rows that were changed in the dtShipments DataTable. It should be noted, that the code calls the dtShipments.AcceptChanges() method to clear the changes. Otherwise, the code will update changes that have already been made.
Dim dtShipments As DataTable
Dim dtTrucks As DataTable
Dim shipBS As BindingSource
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
dtShipments = GetShipmentsDT()
shipBS = New BindingSource()
shipBS.DataSource = dtShipments
dtTrucks = GetTrucksDT()
dtShipments.AcceptChanges()
DataGridView1.DataSource = shipBS
DataGridView2.DataSource = dtTrucks
End Sub
Private Function GetShipmentsDT() As DataTable
Dim dt = New DataTable()
dt.Columns.Add("ID", GetType(String))
dt.Columns.Add("Truck", GetType(String))
dt.Rows.Add(1)
dt.Rows.Add(2)
dt.Rows.Add(3)
dt.Rows.Add(4)
Return dt
End Function
Private Function GetTrucksDT() As DataTable
Dim dt = New DataTable()
dt.Columns.Add("Truck", GetType(String))
For index = 1 To 10
dt.Rows.Add("Truck" + index.ToString())
Next
Return dt
End Function
Private Sub btnAssign_Click(sender As Object, e As EventArgs) Handles btnAssign.Click
Dim sTruck = DataGridView2.SelectedCells(0).Value.ToString
'Dim drv As DataRowView
For Each row As DataGridViewRow In DataGridView1.SelectedRows
'drv = row.DataBoundItem
'drv("Truck") = sTruck
row.Cells("Truck").Value = sTruck
Next
'DataGridView1.CommitEdit(DataGridViewDataErrorContexts.Commit)
'shipBS.ResetBindings(True)
'DataGridView1.CurrentCell = DataGridView1.Rows(0).Cells(0)
End Sub
Private Function SaveChanges() As Boolean
Try
shipBS.ResetBindings(True)
Dim dtChanges As DataTable = dtShipments.GetChanges()
If (dtChanges IsNot Nothing) Then
MessageBox.Show("There are " & dtChanges.Rows.Count & " rows changed in the data table")
' update sql DB
dtShipments.AcceptChanges()
End If
Return True
Catch ex As Exception
Return False
End Try
End Function
Private Sub btnApplyChanges_Click(sender As Object, e As EventArgs) Handles btnApplyChanges.Click
Dim ChangesMade As Boolean
ChangesMade = SaveChanges()
End Sub
Lastly, since you are using VB… I highly recommend that you set the “Strict” option to “ON.” When this option is ON, it will flag possible errors that you may be missing. To do this for all future VB solutions, open VS, close any solutions that may be open, then go to Tools->Options->Projects and Solutions->VB Defaults, then set “Option Strict” to “On.” The default setting is off.
I hope this makes sense and helps.

What is the correct event to use to catch a new row when the user has finished editing?

I need to add several bits of data to my record such as created date and createdby when a user adds a new row in a datatable.
I am looking for the correct event on the bindingsource to catch this so i can add the information then save the record before the user moves onto the next row.
C# seems to have a RowEditEnding event on the datagrid but a)i'm not using C# and b) I can see from searching that its better to work on the datasource, which in this case is a bound datagrid so i presume i should be looking at the bindingsource object but there is not an obvious event to choose.
I think need something like currentchanged with condition if isdirty then...
Please can someone point me in the right direction here.
thanks
john
this seems to work
Private Sub TblOppQuoteDetailBindingSource_CurrentChanged(sender As Object, e As EventArgs) Handles TblOppQuoteDetailBindingSource.CurrentChanged
If sender.current IsNot Nothing Then
If sender.current.IsNew Then
Dim nr As DataRowView = sender.current
nr.Item("OppQuoteID") = 2
nr.Item("Created") = Now
nr.Item("CreatedBy") = G_UserName
ElseIf sender.current.isedit Then
Dim nr As DataRowView = sender.current
nr.Item("OppQuoteID") = 2
nr.Item("Updated") = Now
nr.Item("UpdatedBy") = G_UserName
End If
End If
End Sub
is this the correct way?

How to remove a Specific Button from Flow Layout Panel

I am creating dynamic buttons, each has a unique Tag, each tag is then sent to a list box. i want to be able to select the tag from the list box and click a button to remove the button with that specific tag i tried making this code but no go....
Private Sub cmdRemove_Click(sender As Object, e As EventArgs) Handles cmdRemove.Click
Try
Dim curItem As String = RmvList.SelectedItem.ToString()
MsgBox(curItem)
For Each Button As System.Windows.Forms.Control In Main.FloLay.Controls
If Button.Tag = curItem Then
Main.FloLay.Controls.RemoveAt(CurItem)
End If
Next
Catch ex1 As Exception
End Try
End Sub
How can i Achieve this Feat? Thank you In Advance.
RemoveAt takes a number as parameter that is the index in the collection.
You have to do two things:
Use Remove instead of RemoveAt
Exit the for loop
So the code:
For Each Button As System.Windows.Forms.Control In Main.FloLay.Controls
If Button.Tag = curItem Then
Main.FloLay.Controls.Remove(Button)
Exit For
End If
Next
The first change is obvious, the second change is needed because you alter the enumerator when you remove the button and it would throw an exception if you try to continue the for loop after the deletion.
If you want to delete multiple items you can use the following trick:
For i = Main.FloLay.Controls.Count -1 To 0 Step -1
If Main.FloLay.Controls(i).Tag = curItem Then
Main.FloLay.Controls.RemoveAt(i) 'Here you actually provide the index!
End If
Next
This will work because you don't alter any index yet to come be deleting an item if you move backwards through the collection.

Problems when calling a public sub

I'm facing a deadend When trying to call this sub :
Public Sub backblue(ByVal frm As Form, ByVal boxname As String)
For i = 1 To 3
CType(frm.Controls(boxname & i.ToString()), TextBox).BackColor = Color.LightBlue
Next
End Sub
with button click event :
Private Sub Button1_click and bla bla....
backblue(Me, "txb1_")
End Sub
Can anybody show me a suggestion to fix the code.
It throws "Object Referrence not set to an instance bla bla" error
For information the textbox names are :
txb1_1 , txb1_2 , txb1_3
(these are some of the many textboxes in the form that i want its bakcolor changed)
and these three textboxes are already created through designer, not from execution.
i did check the textboxes names and there's nothing wrong.
the form class is also public.
if they are the only textboxs on said form you can just loop through
For Each box as Textbox In frm.Controls
box.BackColor = Color.LightBlue
Next
This error will occur if you do not declare the Form class to be public.
Also, make sure the textbox names are really correct, although this will probably cause a different error.
If you create the textboxes during execution, make sure they are initialized with New and added to the form's Controls collection.
Try this....
Public Sub backblue(ByVal frm As Form, ByVal prefix As String)
For i = 1 To 3
Dim bxName as String = prefix & i.ToString()
Dim bx as TextBox = CType(frm.Controls(bxName), TextBox)
If bx Is Nothing Then
MsgBox("Unable to find text box " +bxName)
Dim mtch() As Control = frm.Controls.Find(bxName, true)
If mtch.Length> 0 then
bx = mtch(0)
Else
Continue For
End if
End If
Bx.BackColor = Color.LightBlue
Next
End Sub
Although, a better solution would be to either create the textboxes inside a control and pass that control to BackBlue or to create an collection that has the controls and pass that in. Which brings up what is most likely yor problem your control is contained in a sub component and thus is not in the main form control collection
Alternative, you could use either the tag of the control or create a component control that implements IExtenderProvider and add it to the form --all of the above would effectively allow you to define the controls and/how they should be handled at designtime.
It may really seem that the names generated by this loop may not be the names of the original textboxes. My suggestion is before setting this Color property verify that the names generated by this loop are indeed the actual names. Maybe output this in a messagebox:
MessageBox.Show(boxname & i.ToString()) for each loop before you set the property

Limiting my result set in VB

So I can fill the combobox I have going in Visual Studio just how I want with ALL results with the following:
Dim pnum As New List(Of String)
For Each polnumber As InsuredDataSet.Claims_InsuredRow In Me.InsuredDataSet.Claims_Insured
pnum.Add(polnumber.Policy_Number)
Next
pnum.Reverse()
Me.Policy_NumberComboBox.DataSource = pnum
Awesome. Now I want to limit the pnum by taking what was input/selected from Insured_NameTextBox on the form and only returning the Policy_Number with a matching Insured_Name. I figure this can be performed with an If statement, but everything I try (stringcompare, InsuredName_TextBox = Me.InsuredDataSet.ClaimsInsured, etc.) either doesn't limit the results OR limits the results entirely so nothing shows up. Any idea where to put the If statement and what should be compared?
UPDATE:
I think there is some confusion so I'm including the entire load sub below:
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
'TODO: This line of code loads data into the 'IncidentsDataSet.Claims_Incidents' table. You can move, or remove it, as needed.
Me.Claims_IncidentsTableAdapter.Fill(Me.IncidentsDataSet.Claims_Incidents)
'TODO: This line of code loads data into the 'InsuredDataSet.Claims_Insured' table. You can move, or remove it, as needed.
Me.Claims_InsuredTableAdapter.Fill(Me.InsuredDataSet.Claims_Insured)
'textbox autocomplete mode
Dim Iname As New AutoCompleteStringCollection()
For Each insname As InsuredDataSet.Claims_InsuredRow In Me.InsuredDataSet.Claims_Insured
Iname.Add(insname.Insured_Name)
Next
Me.Insured_NameTextBox.AutoCompleteCustomSource = Iname
'combobox autocomplete code (now sorting by last included!)
Dim pnum As New List(Of String)
For Each polnumber As InsuredDataSet.Claims_InsuredRow In Me.InsuredDataSet.Claims_Insured
pnum.Add(polnumber.Policy_Number)
Next
pnum.Reverse()
Me.Policy_NumberComboBox.DataSource = pnum
End Sub
Try something like this:
Me.Policy_NumberComboBox.DataSource = InsuredDataSet.Claims_Insured.Where(Function(r) r.Insured_Name = Insured_NameTextBox.Text).Select(Function(r) r.Policy_Number).Reverse()
We're getting closer. Based on the update to your question, you're running this code when the form loads. However, at the point where the form loads, your textbox will always be empty. What do you do when the value in the textbox changes, to re-filter your data?
This is C#
Me.InsuredDataSet.Claims_Insured.Where(x => x.Insured_Name == Insured_NameTextBox.Text);