I am relatively new to the whole .NET thing, coming from a VB classic background.
On my form I have a tabcontrol, with 4 tabs. Most of the code is handled using a shared handler, but for the others I have to write a handler for each.
How can I optimize these routines into a single procedure?
Private Sub cboCalc0_SelectedIndexChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cboCalc0.SelectedIndexChanged
'Page 0
If (Not (IsNothing(trvSignals0.SelectedNode)) And txtSignalName0.Enabled = True) AndAlso trvSignals0.SelectedNode.Level = 3 Then
tempChannelProp(0, trvSignals0.SelectedNode.Tag).CalcVariant = cboCalc0.SelectedIndex
End If
End Sub
Private Sub cboCalc1_SelectedIndexChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cboCalc1.SelectedIndexChanged
'Page 1
If (Not (IsNothing(trvSignals1.SelectedNode)) And txtSignalName0.Enabled = True) AndAlso trvSignals1.SelectedNode.Level = 3 Then
tempChannelProp(1, trvSignals1.SelectedNode.Tag).CalcVariant = cboCalc1.SelectedIndex
End If
End Sub
Private Sub cboCalc2_SelectedIndexChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cboCalc2.SelectedIndexChanged
'Page 2
If (Not (IsNothing(trvSignals2.SelectedNode)) And txtSignalName2.Enabled = True) AndAlso trvSignals2.SelectedNode.Level = 3 Then
tempChannelProp(2, trvSignals2.SelectedNode.Tag).CalcVariant = cboCalc2.SelectedIndex
End If
End Sub
Private Sub cboCalc3_SelectedIndexChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cboCalc3.SelectedIndexChanged
'Page 3
If (Not (IsNothing(trvSignals3.SelectedNode)) And txtSignalName3.Enabled = True) AndAlso trvSignals3.SelectedNode.Level = 3 Then
tempChannelProp(3, trvSignals3.SelectedNode.Tag).CalcVariant = cboCalc3.SelectedIndex
End If
End Sub
I have handled the other bits as follows, and it works great, but I just cannot figure out how to do it with code like that above.
Private Sub trvSignals_AfterCheck(ByVal sender As Object, ByVal e As System.Windows.Forms.TreeViewEventArgs) Handles trvSignals0.AfterCheck, trvSignals1.AfterCheck, trvSignals2.AfterCheck, trvSignals3.AfterCheck
'Handles Page 0,1,2,3
sender.SelectedNode = e.Node
If e.Node.Level = 3 Then
tempChannelProp(sender.tag, e.Node.Tag).Active = e.Node.Checked
End If
End Sub
I use the tag property of the control of each page to hold either 0,1,2 or 3 as appropriate.
Thanks
Graham
Create control arrays that you can reference by index, and put the index in each controls' Tag.
Class MyForm
Private ReadOnly cboCalcArray As ComboBox()
Private ReadOnly trvSignalsArray As TreeView()
Private ReadOnly txtSignalNameArray As TextBox()
Public Sub New()
InitializeComponent()
cboCalcArray = new ComboBox() {cboCalc0, cboCalc1, cboCalc2, cboCalc3}
trvSignalsArray = new TreeView() {trvSignals0, trvSignals1, trvSignals2, trvSignals3}
txtSignalNameArray = new TextBox() {txtSignalName0, txtSignalName1, txtSignalName2, txtSignalName3}
End Sub
Private Sub cboCalcX_SelectedIndexChanged(ByVal sender As Object, ByVal e As EventArgs) _
Handles cboCalc0.SelectedIndexChanged,
cboCalc1.SelectedIndexChanged,
cboCalc2.SelectedIndexChanged,
cboCalc3.SelectedIndexChanged
Dim index As Integer = sender.Tag
If (Not (IsNothing(trvSignalsArray(index).SelectedNode)) And txtSignalNameArray(index).Enabled = True) AndAlso trvSignals2.SelectedNode.Level = 3 Then
tempChannelProp(index, trvSignalsArray(index).SelectedNode.Tag).CalcVariant = cboCalcArray(index).SelectedIndex
End If
End Sub
End Class
You can leverage a couple of features:
1) The VB Handles statement can hook multiple control events to the same method.
2) You can get the combobox from the sender, then extract the index.
3) From the index, you can find the other associated controls.
Private Sub cboCalc_SelectedIndexChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cboCalc0.SelectedIndexChanged, cboCalc1.SelectedIndexChanged, cboCalc2.SelectedIndexChanged, cboCalc3.SelectedIndexChanged
Dim oCombo As ComboBox
oCombo = DirectCast(sender, ComboBox)
Dim wIndex As Integer
Dim oTree As TreeView
Dim oTextBox As TextBox
wIndex = CInt(oCombo.Name.Substring(oCombo.Name.Length - 1))
oTree = DirectCast(Me.Controls.Find("trvSignals" & wIndex.ToString, True)(0), TreeView)
oTextBox = DirectCast(Me.Controls.Find("txtSignalName" & wIndex.ToString, True)(0), TextBox)
If oTree.SelectedNode IsNot Nothing AndAlso oTextBox.Enabled AndAlso oTree.SelectedNode.Level = 3 Then
tempChannelProp(wIndex, oTree.SelectedNode.Tag).CalcVariant = oCombo.SelectedIndex
End If
End Sub
There is likely a more elegant solution to your problem somewhere. I find that, if I am defining the same controls and methods for multiplpe tabs on a tab control, it might be time to examine my design . . . Not always, though. :-)
This might be a little less clumsy, although using Select Case to choose between otherwise identical configurations of UI elements gives me the heebie-jeebies (It DOES get all your code into one handler, however):
Private Sub PerformCalcs(ByVal sender As Object, ByVal e As System.EventArgs) _
Handles _
cboCalc0.SelectedIndexChanged,
cboCalc1.SelectedIndexChanged,
cboCalc2.SelectedIndexChanged,
cboCalc3.SelectedIndexChanged
Dim cboCalc As ComboBox = DirectCast(sender, ComboBox)
' The Parent of the combo box is the TabPage, and the Parent of the TabPage is the TabControl:
Dim t As TabControl = cboCalc.Parent.Parent
Dim SelectedNode As TreeNode
Dim TxtSignalName As TextBox
' The TabControl knows the index value of the currently selected tab:
Select Case t.SelectedIndex
Case 0 'Page 0
SelectedNode = trvSignals0.SelectedNode
TxtSignalName = txtSignalName0
Case 1 ' Page 1
SelectedNode = trvSignals1.SelectedNode
TxtSignalName = txtSignalName1
Case 2 ' Page 2
SelectedNode = trvSignals2.SelectedNode
TxtSignalName = txtSignalName2
Case 3 ' Page 3
SelectedNode = trvSignals3.SelectedNode
TxtSignalName = txtSignalName3
Case Else ' Ooops! Something horrible happened!
Throw New System.Exception("You have passed an invalid Control as a parameter")
End Select
If (Not (IsNothing(SelectedNode)) And TxtSignalName.Enabled = True) AndAlso trvSignals3.SelectedNode.Level = 3 Then
tempChannelProp(3, SelectedNode.Tag).CalcVariant = cboCalc.SelectedIndex
End If
End Sub
Related
I created this piece of code to illustrate the idea, it uses a combobox and a textbox
Private Sub ComboBox2_DropDown(sender As Object, e As EventArgs) Handles ComboBox2.DropDown
ComboBox2.Items.Clear()
ComboBox2.Items.Add("0001 | Apple")
ComboBox2.Items.Add("0002 | Pear")
ComboBox2.Items.Add("0003 | Banana")
ComboBox2.Items.Add("0004 | Pineapple")
ComboBox2.Items.Add("0005 | Cherry")
End Sub
Private Sub ComboBox2_DropDownClosed(sender As Object, e As EventArgs) Handles ComboBox2.DropDownClosed
Dim selecteditem As String = ComboBox2.Items(ComboBox2.SelectedIndex)
ComboBox2.Text = Strings.Left(selecteditem,4)
TextBox2.Text = Strings.Left(selecteditem,4)
End Sub
When I select an item from the combobox what happens is that the combobox keeps showing the whole string while the textbox only shows the first 4 characters.
How can I overwrite the combobox text after I close the combobox?
* edit *
I tried a combo of the solutions but ran into a problem because the data was bound to a datasource so it's not possible to change the item.
This is the new code:
Private Sub ComboBox2_DropDown(sender As Object, e As EventArgs) Handles ComboBox2.DropDown
SQL.ExecQuery($"select ID, Name, RTRIM(ID + ' | ' + Name) as SingleColumn from GCCTEST.dbo.tblFruit")
ComboBox2.DataSource = SQL.DBDT
ComboBox2.DisplayMember = "SingleColumn"
End Sub
Private Sub ComboBox2_DropDownClosed(sender As Object, e As EventArgs) Handles ComboBox2.DropDownClosed
ComboBox2.DisplayMember = "ID"
ComboBox2.SelectedIndex = 0
End Sub
Now I only need to have the 0 be the index I chose...
The following should work.
If not necessary, don't populate the combobox on every drop-down, instead call the FillComboBox-method when loading the Form.
Private Sub FillComboBox()
SQL.ExecQuery($"select ID, Name, RTRIM(ID + ' | ' + Name) as SingleColumn from GCCTEST.dbo.tblFruit")
ComboBox2.DataSource = SQL.DBDT
ComboBox2.DisplayMember = "ID"
ComboBox2.ValueMember = "ID"
End Sub
Private Sub ComboBox2_DropDown(sender As Object, e As EventArgs) Handles ComboBox2.DropDown
Me.ComboBox2.DisplayMember = "SingleColumn"
End Sub
Private Sub ComboBox2_SelectionChangeCommitted(sender As Object, e As EventArgs) Handles ComboBox2.SelectionChangeCommitted
Dim selV As Object = Me.ComboBox2.SelectedValue
Me.TextBox2.Text = CStr(selV)
Me.ComboBox2.DisplayMember = "ID"
'Set the current value again, otherwise the combobox will always display the first item
Me.ComboBox2.SelectedValue = selV
End Sub
I used a few properties and .net String.SubString method instead of the old vb6 Strings.Left.
Private Sub ComboBox1_DropDownClosed(sender As Object, e As EventArgs) Handles ComboBox1.DropDownClosed
Dim SelectedString As String = ComboBox1.SelectedItem.ToString
Dim ChangedString As String = SelectedString.Substring(0, 4)
Dim index As Integer = ComboBox1.SelectedIndex
ComboBox1.Items(index) = ChangedString
End Sub
You can fill your combo box one by one to avoid binding problems as follows...
Private Sub ComboBox1_DropDown(sender As Object, e As EventArgs) Handles ComboBox1.DropDown
Using cn As New SqlConnection("Your connection string")
Using cmd As New SqlCommand("Select ID, Name From tblFruit;", cn)
cn.Open()
Using dr As SqlDataReader = cmd.ExecuteReader
ComboBox1.BeginUpdate()
While dr.Read
ComboBox1.Items.Add(dr(0).ToString & " | " & dr(1).ToString)
End While
ComboBox1.EndUpdate()
End Using
End Using
End Using
You can solve this graphical problem putting a Label in your Form and moving it over your ComboBox.
Private Sub Form1_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
Me.Label1.AutoSize = False
Me.Label1.BackColor = Me.ComboBox1.BackColor
Me.Label1.Location = New Point(Me.ComboBox1.Location.X + 1, Me.ComboBox1.Location.Y + 1)
Me.Label1.Size = New Size(Me.ComboBox1.Width - 18, Me.ComboBox1.Height - 2)
Me.ComboBox1.Items.Add("0001 | Apple")
Me.ComboBox1.Items.Add("0002 | Pear")
Me.ComboBox1.Items.Add("0003 | Banana")
Me.ComboBox1.Items.Add("0004 | Pineapple")
Me.ComboBox1.Items.Add("0005 | Cherry")
End Sub
Private Sub ComboBox1_SelectedIndexChanged(sender As Object, e As System.EventArgs) Handles ComboBox1.SelectedIndexChanged
Me.Label1.Text = Trim(Me.ComboBox1.SelectedItem.Split("|")(0))
End Sub
Please note that:
you can populate your ComboBox a single time during Form load event
you can use SelectedIndexChanged instead of DropDown and DropDownClosed event
if this is not a graphical problem please give a look at DisplayMember and ValueMember properties
First of all excuse me for my poor grammar and vocabulary :)
please see this source and run it:
Public Class Form1
Public pointX As Integer
Public pointY As Integer = 32
Public dynamicText As TextBox
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
pointX = 330
For i = 1 To 4
dynamicText = New Windows.Forms.TextBox
dynamicText.Name = "T" + Trim(Str(i))
dynamicText.Text = ""
dynamicText.Location = New Point(pointX, pointY)
dynamicText.Size = New Size(100, 20)
Me.Controls.Add(dynamicText)
pointX = pointX - 106
Next
End Sub
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
pointX = 330
pointY = pointY + 26
For i = 1 To 4
dynamicText = New Windows.Forms.TextBox
dynamicText.Name = "T" + Trim(Str(i))
dynamicText.Text = ""
dynamicText.Location = New Point(pointX, pointY)
dynamicText.Size = New Size(100, 20)
Me.Controls.Add(dynamicText)
pointX = pointX - 106
AddHandler dynamicText.Click, AddressOf printHello1
Next
End Sub
Private Sub printHello1(ByVal sender As System.Object, ByVal e As System.EventArgs)
MsgBox(dynamicText.Name)
If dynamicText.Name = "T1" Then MsgBox("Oh! this is T1")
End Sub
End Class
why If never is not true?!
why MsgBox(dynamicText.Name) always return T4?!
i want all controlls to be access by name or array of names.
please help me thank you. :)
The global variable dynamicText takes the value of the last TextBox added in the loop inside the Button1_Click event. This happens to be the control named T4. You don't really need a global variable in this case. You can cast the sender parameter to a TextBox instance because the sender parameter is the control that has raised the event.
Private Sub printHello1(ByVal sender As System.Object, ByVal e As System.EventArgs)
Dim txt = CType(sender, "TextBox")
if txt IsNot Nothing then
MsgBox(txt.Name)
If txt.Name = "T1" Then MsgBox("Oh! this is T1")
End If
End Sub
You also don't need to recreate the controls again in the button click event. The action executed in the form load event is enough (You could add the AddHandler there). Global variables are dangerous, avoid them when possible.
See if this is acceptable. Place a panel at the bottom of your form, set Dock to Bottom, add a single button to the panel and a TextBox. Place a FlowLayoutPanel onto the form, Dock = Fill, AutoScroll = True.
The code below creates the amount of TextBox controls as inputted into TextBox. Each newly created TextBox a click event is added with simple logic.
Form code
Public Class Form1
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim count As Integer = 0
If Integer.TryParse(TextBox1.Text, count) Then
Dim demo = New TextBoxCreate(FlowLayoutPanel1, "Demos", count)
demo.CreateTextBoxes()
End If
End Sub
End Class
Class code (add a new class to the project, name it TextBoxCreate.vb)
Public Class TextBoxCreate
Public Property TextBoxes As TextBox()
Public Property TextBoxBaseName As String
Public Property TextBoxCount As Integer
Public Property ParentControl As Control
Public Sub New(
ByVal ParentControl As Control,
ByVal BaseName As String,
ByVal Count As Integer)
Me.ParentControl = ParentControl
Me.TextBoxBaseName = BaseName
Me.TextBoxCount = Count
End Sub
Public Sub CreateTextBoxes()
Dim Base As Integer = 10
TextBoxes = Enumerable.Range(0, TextBoxCount).Select(
Function(Indexer)
Dim b As New TextBox With
{
.Name = String.Concat(TextBoxBaseName, Indexer + 1),
.Text = (Indexer + 1).ToString,
.Width = 150,
.Location = New Point(25, Base),
.Parent = Me.ParentControl,
.Visible = True
}
AddHandler b.Click, Sub(sender As Object, e As EventArgs)
Dim tb As TextBox = CType(sender, TextBox)
If tb.Name = TextBoxBaseName & "1" Then
tb.Text = "Got it"
Else
MessageBox.Show(tb.Name)
End If
End Sub
Me.ParentControl.Controls.Add(b)
Base += 30
Return b
End Function).ToArray
End Sub
End Class
I am new to visual basic so hopefully this is a simple question. I have a menu with buttons to call different forms. The forms are designed and have labels and text fields and buttons and so on. From the main menu I have tried calling the forms two different ways. One way the forms open and look correct and function. The other way the form opens as a small blank square with no fields. Ultimately I want to create a set of List objects when the main menu opens and pass them back and forth to the other forms for input and processing. I'm using parallel Lists as a temporary database for a simple school lab. I just don't see what is wrong with the way I am calling the form. I haven't even bothered worrying about passing the List objects properly yet.
Public Class frmMain
Dim arrGames As New List(Of String)
Dim arrDates As New List(Of String)
Dim arrPrices As New List(Of Decimal)
Dim arrSeats As New List(Of Integer)
Private Sub btnEnterGames_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnEnterGames.Click
'NewEnter.Visible = True
Dim frmEnter As New NewEnter(arrGames, arrDates, arrPrices, arrSeats)
frmEnter.ShowDialog()
End Sub
Private Sub btnReports_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnReports.Click
'Reports.Visible = True
Dim frmReports As New Reports(arrGames, arrDates, arrPrices, arrSeats)
frmReports.Visible = True
End Sub
Private Sub btnSellTickets_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnSellTickets.Click
'SellTickets.Visible = True
Dim frmSell As New SellTickets(arrGames, arrDates, arrPrices, arrSeats)
frmSell.Visible = True
End Sub
Private Sub btnExit_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnExit.Click
Close()
End Sub
End Class
This is the code for the form NewEnter. I have the New routine which accepts the 4 Lists and basically does nothing else. Doing the "'NewEnter.Visible = True" in the main menu will load the form correctly but I have to comment out the New sub routine in the forms or there is an error.
Public Class NewEnter
Private _arrGames As List(Of String)
Private _arrDates As List(Of String)
Private _arrPrices As List(Of Decimal)
Private _arrSeats As List(Of Integer)
Sub New(ByVal arrGames As List(Of String), ByVal arrDates As List(Of String), ByVal arrPrices As List(Of Decimal), ByVal arrSeats As List(Of Integer))
' TODO: Complete member initialization
' _arrGames = arrGames
' _arrDates = arrDates
' _arrPrices = arrPrices
' _arrSeats = arrSeats
End Sub
Private Sub btnSaveGame_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnSaveGame.Click
Dim arrGames As New List(Of String)
Dim arrDates As New List(Of String)
Dim arrPrices As New List(Of Decimal)
Dim arrSeats As New List(Of Integer)
Dim strGame As String
Dim strPrice As String
Dim strSeats As String
Dim intSeats As Integer
Dim decPrice As Decimal
Dim bolGameErr As Boolean
Dim bolDateErr As Boolean
Dim bolPriceErr As Boolean
Dim bolSeatErr As Boolean
strGame = txtGame.Text
strPrice = txtPrice.Text
strSeats = txtSeats.Text
'~~~~~~~~~~~~verify a game is entered
If String.IsNullOrEmpty(strGame) Or String.IsNullOrWhiteSpace(strGame) Then
bolGameErr = True
Else
'~~~~~~~~~~~~verify price is numeric
If IsNumeric(strPrice) Then
decPrice = strPrice
'~~~~~~~~~~~~~~~verify seats are numeric
If IsNumeric(strSeats) Then
intSeats = Convert.ToInt32(strSeats)
' ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ add elements to array lists
arrGames.Add(New String(strGame))
arrDates.Add(dtpDate.Text)
arrPrices.Add(New Decimal(decPrice))
arrSeats.Add(intSeats)
lblSaveSuccessful.Visible = True
ClearInput()
' ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ add elements to array lists
Else
bolSeatErr = True
End If
Else
bolPriceErr = True
End If
End If
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Check flags for input errors
If bolDateErr = True Then
lblErr.Text = "Invalid date"
lblErr.Visible = True
End If
If bolGameErr = True Then
lblErr.Text = "Must enter a game name"
lblErr.Visible = True
txtGame.Focus()
End If
If bolDateErr = True And bolGameErr = True Then
lblErr.Text = "Must enter a game name and valid date"
lblErr.Visible = True
txtGame.Focus()
End If
If bolPriceErr = True Then
lblPriceErr.Visible = True
txtPrice.Text = ""
txtPrice.Focus()
End If
If bolSeatErr = True Then
lblSeatErr.Visible = True
txtSeats.Text = ""
txtSeats.Focus()
End If
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Check flags for input error
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Display output
Dim i As Integer
i = 0
lblData.Text = arrGames.Count.ToString
Do While i < arrGames.Count
lblData.Text = Convert.ToString(arrGames(i)) & " on " & Convert.ToString(arrDates(i)) & " Price: " & _
Convert.ToString(arrPrices(i)) & " Available Seats: " & Convert.ToString(arrSeats(i))
i += 1
Loop
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Display output
lblData.Visible = True
End Sub
Private Sub ClearInput()
'lblErr.Visible = False
'lblPriceErr.Visible = False
'lblSeatErr.Visible = False
txtGame.Text = ""
txtPrice.Text = ""
txtSeats.Text = ""
txtGame.Focus()
End Sub
Public Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
'Me.Visible = True
'Me.BackColor = Color.BurlyWood
'Me.ResumeLayout()
'Me.Activate()
'Me.Focus()
'Me.Show()
'Me.lblGameHdr.Visible = True
End Sub
Private Sub btnExit_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnExit.Click
Close()
End Sub
End Class
Add InitializeComponent() to your constructor class.
This is added by default to the New (constructor) function of all Visual Basic forms. It requires it to set-up the UI components on the form.
I prepared a small project demonstrating my issue. It consists of two data tables with a parent-child relation. I use vb.net 2010 with framework 4.0.
- Start a new WinForm project.
- On Form1 designer, drag one BindingNavigator, one Button and two ComboBoxes.
- In code behind, copy code below:
Public Class Form1
Private dsMain As DataSet
Private WithEvents bndSource As New BindingSource
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
dsMain = New DataSet
'Create tables
Dim dtContacts As New DataTable("Contacts")
Dim col As DataColumn = dtContacts.Columns.Add("IDContact", GetType(Integer))
col.AllowDBNull = False
col.AutoIncrement = True
col.Unique = True
dtContacts.Columns.Add("FullName")
col = dtContacts.Columns.Add("IDCountry", GetType(Integer))
col.AllowDBNull = True
dsMain.Tables.Add(dtContacts)
Dim dtCountries As New DataTable("Countries")
col = dtCountries.Columns.Add("IDCountry", GetType(Integer))
col.AllowDBNull = False
col.AutoIncrement = True
col.Unique = True
dtCountries.Columns.Add("A2ISO")
dtCountries.Columns.Add("CountryName")
dsMain.Tables.Add(dtCountries)
'Add relation
Dim rel As New DataRelation("rel", dtCountries.Columns("IDCountry"), dtContacts.Columns("IDCountry"))
dsMain.Relations.Add(rel)
'Populate parent table
dtCountries.Rows.Add(1, "AF", "Afghanistan")
dtCountries.Rows.Add(2, "AL", "Albania")
dtCountries.Rows.Add(3, "DZ", "Algeria")
'Populate child table
dtContacts.Rows.Add(1, "First Contact", 3)
dtContacts.Rows.Add(2, "Second Contact", 1)
'Set bindings
bndSource.DataSource = dtContacts.DefaultView
BindingNavigator1.BindingSource = bndSource
ComboBox1.DataSource = dtCountries
ComboBox1.DisplayMember = "A2ISO"
ComboBox1.ValueMember = "IDCountry"
ComboBox1.DataBindings.Add("SelectedValue", bndSource, "IDCountry")
ComboBox2.DataSource = dtCountries
ComboBox2.DisplayMember = "CountryName"
ComboBox2.ValueMember = "IDCountry"
ComboBox2.DataBindings.Add("SelectedValue", bndSource, "IDCountry")
dsMain.AcceptChanges()
End Sub
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
ComboBox1.Text = Nothing 'Set IDCountry to DBNull, same as ComboBox1.SelectedIndex = -1
ComboBox2.Text = Nothing
End Sub
End Class
- Run the project.
- Try to navigate back and forth, and change items in the ComboBoxes: all is correct.
- Now set IDCountry = Null in Contacts table, as it is allowed. To achieve this, click on Button1.
- In ComboBox1, if you select the same item as it was before clicking the button, you see the behavior is not correct anymore: ComboBox2 doesn't update accordingly with ComboBox1 but remains empty.
- If you select another item, from now on all is correct again.
Is this a bug in .net data binding? If so, is there any workaround?
Thanks in advance.
This is the workaround I found. I didn't find the reason of the behavior I observed, but this code works for me:
Private Sub ComboBox1_SelectedIndexChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles ComboBox1.SelectedIndexChanged
If ComboBox1.SelectedIndex < ComboBox2.Items.Count Then ComboBox2.SelectedIndex = ComboBox1.SelectedIndex
End Sub
Private Sub ComboBox2_SelectedIndexChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles ComboBox2.SelectedIndexChanged
If ComboBox2.SelectedIndex < ComboBox1.Items.Count Then ComboBox1.SelectedIndex = ComboBox2.SelectedIndex
End Sub
It simply makes ComboBox selected indexes equal.
Thank you anyway.
I'm trying to make a dynamic input form. But to do this I need to be able to pass multiple adressof's to 1 sub. Is this possible?
Here is my code:
Public Function AddNewcombobox() 'As System.Windows.Forms.ComboBox
Dim cmbSoort As New System.Windows.Forms.ComboBox()
Me.Controls.Add(cmbSoort)
cmbSoort.Top = cLeft
cmbSoort.Left = 62
cmbSoort.Items.Add("Maak een keuze")
cmbSoort.Items.Add("Behuizingen")
cmbSoort.Items.Add("Moederborden")
cmbSoort.Items.Add("Processoren")
cmbSoort.Items.Add("Grafische kaarten")
cmbSoort.Items.Add("Geheugen")
cmbSoort.Items.Add("DVD/Blu-ray")
cmbSoort.Items.Add("Harddisks")
cmbSoort.Items.Add("SSD")
cmbSoort.Items.Add("Voedingen")
cmbSoort.Items.Add("Invoerapparaten")
cmbSoort.Items.Add("Monitoren")
cmbSoort.SelectedIndex = 0
cmbSoort.Name = "Soort" & mintI
AddHandler cmbSoort.SelectedIndexChanged, AddressOf IndexVeranderd
Return cmbSoort
End Function
Public Sub AddNewName()
Dim cmbName As New System.Windows.Forms.ComboBox()
Me.Controls.Add(cmbName)
cmbName.Top = cLeft
cmbName.Left = 292
cmbName.Items.Add("Maak een keuze")
cmbName.Name = "Naam" & mintI
cmbName.Enabled = False
CmbPrijs.Enabled = False
txtStuks.Enabled = False
'AddHandler AddNewcombobox.SelectedIndexChanged, AddressOf IndexVeranderd
cLeft = cLeft + 40
mintI += 1
End Sub
Private Sub cmbNaam_SelectedIndexChanged(ByVal sender As System.Object, ByVal e As System.EventArgs)
'CmbPrijs.SelectedIndex = CmbNaam.SelectedIndex
End Sub
Private Sub IndexVeranderd(ByVal sender As System.Object, ByVal e As System.EventArgs)
Dim ComboVeranderd = DirectCast(sender, ComboBox)
Dim combonaam = DirectCast(sender, ComboBox)
MsgBox(combonaam.ToString)
If ComboVeranderd.SelectedIndex = 0 Then
'ComboNaam.Enabled = False
txtStuks.Enabled = False
End If
For i = 0 To EasybyteDataSet.Stock.Rows.Count - 1
If ComboVeranderd.SelectedItem = EasybyteDataSet.Stock.Rows(i)("Soort") Then
'ComboNaam.Enabled = True
txtStuks.Enabled = True
'ComboNaam.Items.Add(EasybyteDataSet.Stock.Rows(i)("Product naam"))
CmbPrijs.Items.Add(EasybyteDataSet.Stock.Rows(i)("Prijs"))
End If
Next
End Sub
When cmbSoort's index changes, it should send both cmbSoort and cmbName to the sub IndexVeranderd.
The trick is, cmbSoort and cmbName are generated by the functions when the user presses a button.
Is this possible?
To make a Sub handle multi combobox ..
In your case :
Public Sub AddNewcombobox()
Dim cmbSoort as New ComboBox
Dim cmbName as New ComboBox
'.......... fill cmbSoort properties
'.......... fill cmbName properties
Controls.Add(cmbSoort)
AddHandler cmbSoort.SelectedIndexChanged, AddressOf IndexVeranderd
Controls.Add(cmbname)
AddHandler cmbName.SelectedIndexChanged, AddressOf IndexVeranderd
End Sub
And the Sub handler
Private Sub IndexVeranderd(ByVal sender As System.Object, ByVal e As System.EventArgs)
Select Case oCB
Case cmbSoort
' ................. code here
Case cmbName
' ................. code here
End Select
End Sub
The names seem to have an number added to them if they are the same so that Soort1 is tied to Naam1 then just separate out the number and access the control directly. This way you only need the handler for cmbSoort.
Dim ComboVeranderd = DirectCast(sender, ComboBox)
Dim combonaam = Me.Controls("Naam"+ComboVeranderd.Name.Substring(ComboVeranderd.Name.Length-1))
I assumed the number is only 1 digit, if not you might have to adjust for more.
If the names don't include a number this would be an efficient way to pair them up