none
How to write AddHandler for multiple ComboBoxes RRS feed

  • Question

  • Hi Everyone:

    My application looks at the construction of a database's table and dynamically creates a data entry form.

    This form contains two foreign key columns: CountryID and StateID. I wish to modify the dynamically created forms so they will have a combo box for the foreign key columns.

    However, in order to make the combo boxes functional I have to tell it which column is the ValueMember and which column is the DisplayMember. I have a routine that creates the combo boxes and labels for the required parent tables.

    Private Function Create_TLPCboBoxes(ByVal tlpCtrl As TableLayoutPanel,
                                        ByVal dtParentTbls As DataTable) _
                                        As Control
            Dim I As Long
            Dim strName As String
            Dim lblCtrl As New Label
            Dim cboBox As New ComboBox
            Dim schemaTbl As New DataTable
            With tlpCtrl
                .SuspendLayout()
                .Controls.Clear()
                .AutoSize = True
                .AutoSizeMode = AutoSizeMode.GrowAndShrink
                .BackColor = NewColor
                .CellBorderStyle = TableLayoutPanelCellBorderStyle.Single
                .ColumnStyles.Add(New ColumnStyle(SizeType.AutoSize))
                .Location = New Point(528, 10)
                For I = 0 To dtParentTbls.Rows.Count - 1
                    strName = dtParentTbls.Rows(I).Item(0).ToString
                    schemaTbl = objOleDB.Get_OleDB_Tbl_Schema(cnnOleDB, strName)
                    lblCtrl = New Label
                    lblCtrl = Ins_Label_Ctrl(lblCtrl, "ValueMember-" & strName,
                                             "ValueMember-" & strName)
                    'Add label control to Column 1
                    .Controls.Add(lblCtrl, 1, I)
                    cboBox = New ComboBox
                    cboBox = Ins_CboBox_Ctrl(cboBox, "VM_" & strName, schemaTbl)
                    'Add ComboBox to Column 2
                    .Controls.Add(cboBox, 2, I)
                    lblCtrl = New Label
                    lblCtrl = Ins_Label_Ctrl(lblCtrl, "DisplayMember-" & strName,
                                             "DisplayMember-" & strName)
                    'Add label control to Column 1
                    .Controls.Add(lblCtrl, 3, I)
                    cboBox = New ComboBox
                    cboBox = Ins_CboBox_Ctrl(cboBox, "DM_" & strName, schemaTbl)
                    'Add ComboBox to Column 2
                    .Controls.Add(cboBox, 4, I)
                Next I
                .ResumeLayout()
            End With
            Return tlpCtrl
        End Function
        Private Function Ins_CboBox_Ctrl(ByVal ctrlCboBox As ComboBox,
                                         ByVal strName As String,
                                         ByVal dtaSource As DataTable) _
                                         As Control
    
            With ctrlCboBox
                .Font = New Drawing.Font("Comic Sans MS",
                                         10, FontStyle.Regular)
    
                .Size = New Point(120, 100)
                .DataSource = dtaSource
                .DisplayMember = dtaSource.Columns(0).ToString
            End With
            Return ctrlCboBox
        End Function
        Private Function Ins_Label_Ctrl(ByVal ctrlLabel As Label,
                                        ByVal strName As String,
                                        ByVal lblText As String) _
                                        As Control
            With ctrlLabel
                .AutoSize = True
                .Font = New Drawing.Font("Comic Sans MS",
                                         10, FontStyle.Regular)
                .Name = strName
                .Text = lblText
            End With
            Return ctrlLabel
        End Function

    These three routines add the necessary combo boxes to the main form. Here the user will select which column is the ValueMember for tblCountries. Which column is the DisplayMember for tblCountries and so forth for tblStates.

    When the combo boxes are created the ValueMember is prefixed with "VM_". 

    cboBox = Ins_CboBox_Ctrl(cboBox, "VM_" & strName, schemaTbl). The DispalyMember is prefixed with "DM_". cboBox = Ins_CboBox_Ctrl(cboBox, "DM_" & strName, schemaTbl).

    My problem is "How do I write an AddHandler routine to cover this?

    Thanks,



    MRM256

    Monday, January 28, 2019 8:14 PM

All replies

  • Hello,

    Keep in mind the following is conceptual (and works) along with the event can be which ever event you want to subscribe too.

    The following code module contains two extension methods, the first to get all of a specific type of control on a form or in a container and recurse the children controls. The second is basically a "helper". Is there a more simple approach, sure but less flexible.

    Public Module Extensions
        <Runtime.CompilerServices.Extension>
        Public Iterator Function Descendants(Of T As Class)(control As Control) As IEnumerable(Of T)
            For Each child As Control In control.Controls
                Dim thisControl As T = TryCast(child, T)
                If thisControl IsNot Nothing Then
                    Yield CType(thisControl, T)
                End If
    
                If child.HasChildren Then
                    For Each descendant As T In child.Descendants(Of T)()
                        Yield descendant
                    Next descendant
                End If
            Next child
        End Function
    
        <Runtime.CompilerServices.Extension>
        Public Function DescendantsOfComboBox(sender As Control) As List(Of ComboBox)
            Return sender.Descendants(Of ComboBox)().ToList()
        End Function
    End Module
     

    When ready to subscribe to an event for all ComboBox controls on a form or in a container.

    DescendantsOfComboBox.ForEach(
        Sub(comboBox)
            AddHandler comboBox.SelectedIndexChanged,
               AddressOf ComboBoxSelectedIndexChanged
        End Sub)
    

    Event might have some validation e.g.

    Private Sub ComboBoxSelectedIndexChanged(sender As Object, e As EventArgs)
        Dim cb As ComboBox = CType(sender, ComboBox)
    
        If Not String.IsNullOrWhiteSpace(cb.ValueMember) AndAlso Not String.IsNullOrWhiteSpace(cb.DisplayMember) Then
            Console.WriteLine($"{cb.Name}, DM: {cb.DisplayMember}, VM: {cb.ValueMember}")
        End If
    End Sub


    Please remember to mark the replies as answers if they help and unmark them if they provide no help, this will help others who are looking for solutions to the same or similar problem. Contact via my Twitter (Karen Payne) or Facebook (Karen Payne) via my MSDN profile but will not answer coding question on either.
    VB Forums - moderator
    profile for Karen Payne on Stack Exchange, a network of free, community-driven Q&A sites

    Monday, January 28, 2019 9:03 PM
    Moderator
  • Karen,

    I might be exceeding the capabilities of VS 2017.

    BTW - A new problem has reared its ugly head. The way I populate the dynamically generated combo boxes means that I can't select one value in the ValueMember  combo box and a different one in the DisplayMember combo box. When you change ValueMember; DisplayMember changes to the same one.

    What I need is:

    It looks like I will have to approach this from a different angle. Because before I render the data entry form for the selected table - tblCustomers in this case. I need to tell the combo boxes what their ValueMembers and DisplayMembers are. 

    Does this make any sense?

    Thanks,


    MRM256

    Monday, January 28, 2019 11:40 PM
  • I doubt you can exceed VS2017 capabilities only that there is something not counted on in the design. For instance,  depending on how data is setup for controls including containers (DataTable, DataSet, BindingSource component etc).

    Here is a thought, if you set a relationship (DataSet coupled with BindingSource components) up between the main table to the reference tables than there is no need to think about Display and Value member properties. Now the idea is sound but wrapping it all together main consume some brain cells and coffee but it may be a path (or not) to follow. The key is setting up relations and binding everything to one BindingSource for the main table and one BindingSource for each reference table.

    Anyways the method shown in my first reply should have answered your question while the new discovery is just that a new question.


    Please remember to mark the replies as answers if they help and unmark them if they provide no help, this will help others who are looking for solutions to the same or similar problem. Contact via my Twitter (Karen Payne) or Facebook (Karen Payne) via my MSDN profile but will not answer coding question on either.
    VB Forums - moderator
    profile for Karen Payne on Stack Exchange, a network of free, community-driven Q&A sites

    Monday, January 28, 2019 11:55 PM
    Moderator
  • Karen,

    I fixed the problem that Reared its ugly head. I just added a second data table variable. Now it works as intended.

    Can you explain further on how your Iterator function works?

    I need to know were to start :-).

    Thanks,


    MRM256


    • Edited by MRM256 Tuesday, January 29, 2019 4:17 PM Added sentence.
    Tuesday, January 29, 2019 4:10 PM
  • Karen:

    How do I test this?


    MRM256

    Wednesday, January 30, 2019 3:49 PM
  • Hi,

    If your issue is solved, please Mark as answer.

    Best Regards,

    Alex


    MSDN Community Support Please remember to click "Mark as Answer" the responses that resolved your issue, and to click "Unmark as Answer" if not. This can be beneficial to other community members reading this thread. If you have any compliments or complaints to MSDN Support, feel free to contact MSDNFSF@microsoft.com.

    Monday, February 11, 2019 9:22 AM
    Moderator
  • I can't seem to find an acceptable solution to this issue.

    What do I do if I can't flag a specific answer? Remove the post?

    Thanks,


    MRM256

    Monday, February 11, 2019 6:52 PM
  • I can't seem to find an acceptable solution to this issue.

    What do I do if I can't flag a specific answer? Remove the post?

    Thanks,


    MRM256


     No,  do not remove your post.  Others may come along and have a different solution,  or you may find another solution which you could share here.  8)

    If you say it can`t be done then i`ll try it

    Monday, February 11, 2019 7:13 PM
  •  With a quick look at your code,  I can see one thing you need to change for starters.  Create new instances of the combobox and label controls local to the loop instead of locally to the sub on each iteration of the loop.  Then use the AddHandler method to prescribe to whatever events you need from the ComboBoxes.

     You are creating a combobox that is local to the Sub instead of locally to the loop and adding it to the TableLayoutPanel correctlly but,  you use that same variable to create your next new combobox and are loosing any reference you had to the last one you created.  Same with your Labels too.  Notice there is a cboBox1,  cboBox2,  lblCtrl1,  and lblCtrl2 all created locally,  inside the loop.

     I showed adding a handler to all the combobox SelectedIndexChanged events.  I also show the Handler sub for the event and how you can detect which combobox raised the event.

     Changing the function like this should fix the problem...

        Private Function Create_TLPCboBoxes(ByVal tlpCtrl As TableLayoutPanel, ByVal dtParentTbls As DataTable) As Control
            Dim strName As String
            Dim schemaTbl As New DataTable
    
            With tlpCtrl
                .SuspendLayout()
                .Controls.Clear()
                .AutoSize = True
                .AutoSizeMode = AutoSizeMode.GrowAndShrink
                .BackColor = NewColor
                .CellBorderStyle = TableLayoutPanelCellBorderStyle.Single
                .ColumnStyles.Add(New ColumnStyle(SizeType.AutoSize))
                .Location = New Point(528, 10)
                For I As Integer = 0 To dtParentTbls.Rows.Count - 1
                    strName = dtParentTbls.Rows(I).Item(0).ToString
                    schemaTbl = objOleDB.Get_OleDB_Tbl_Schema(cnnOleDB, strName)
    
                    Dim lblCtrl1 As New Label
                    lblCtrl1 = Ins_Label_Ctrl(lblCtrl1, "ValueMember-" & strName, "ValueMember-" & strName)
                    .Controls.Add(lblCtrl1, 1, I)
    
                    Dim cboBox1 As New ComboBox
                    cboBox1 = Ins_CboBox_Ctrl(cboBox1, "VM_" & strName, schemaTbl)
                    AddHandler cboBox1.SelectedIndexChanged, AddressOf cboBox_SelectedIndexChanged
                    .Controls.Add(cboBox1, 2, I)
    
                    Dim lblCtrl2 As New Label
                    lblCtrl2 = Ins_Label_Ctrl(lblCtrl2, "DisplayMember-" & strName, "DisplayMember-" & strName)
                    .Controls.Add(lblCtrl2, 3, I)
    
                    Dim cboBox2 As New ComboBox
                    cboBox2 = Ins_CboBox_Ctrl(cboBox2, "DM_" & strName, schemaTbl)
                    AddHandler cboBox2.SelectedIndexChanged, AddressOf cboBox_SelectedIndexChanged
                    .Controls.Add(cboBox2, 4, I)
                Next I
                .ResumeLayout()
            End With
            Return tlpCtrl
        End Function
    
        Private Sub cboBox_SelectedIndexChanged(sender As Object, e As EventArgs)
            Dim cbx As ComboBox = CType(sender, ComboBox) 'cast the 'sender' to a ComboBox to get a reference to the one that raised this event
            MessageBox.Show("You selected something in " & cbx.Name) 'just to show you can use the combobox's Name or other property to identify which one it is
        End Sub


    If you say it can`t be done then i`ll try it

    • Edited by IronRazerz Monday, February 11, 2019 8:11 PM
    Monday, February 11, 2019 7:52 PM
  • IronRazerz,

    I create my dynamic combo boxes using this function:

    Private Function Ins_CboBox_Ctrl(ByVal ctrlCboBox As ComboBox,
                                     ByVal strName As String,
                                     ByVal dtaSource As DataTable) _
                                     As Control
    
        With ctrlCboBox
            .Font = New Drawing.Font("Comic Sans MS",
                                     10, FontStyle.Regular)
            .Size = New Point(120, 100)
            .DataSource = dtaSource
            .DisplayMember = dtaSource.Columns(0).ToString
        End With
        Return ctrlCboBox
    End Function

    Can I create an AddHandler function in a similar manner? 

    Thanks,



    MRM256

    Thursday, February 14, 2019 8:03 PM
  • IronRazerz,

    I create my dynamic combo boxes using this function:

    Private Function Ins_CboBox_Ctrl(ByVal ctrlCboBox As ComboBox,
                                     ByVal strName As String,
                                     ByVal dtaSource As DataTable) _
                                     As Control
    
        With ctrlCboBox
            .Font = New Drawing.Font("Comic Sans MS",
                                     10, FontStyle.Regular)
            .Size = New Point(120, 100)
            .DataSource = dtaSource
            .DisplayMember = dtaSource.Columns(0).ToString
        End With
        Return ctrlCboBox
    End Function

    Can I create an AddHandler function in a similar manner? 

    Thanks,



    MRM256

     Sorry for the delay.  Yes,  you just need to move the AddHandler statement into the Ins_CboBox_Ctrl sub.  However,  you still need to pass separate new ComboBox instances into the sub as I explained in my last post or it will not do any good.

        Private Function Create_TLPCboBoxes(tlpCtrl As TableLayoutPanel, dtParentTbls As DataTable) As TableLayoutPanel 'return a TableLayoutPanel instead of a Control
            Dim strName As String
            Dim schemaTbl As New DataTable
    
            With tlpCtrl
                .SuspendLayout()
                .Controls.Clear()
                .AutoSize = True
                .AutoSizeMode = AutoSizeMode.GrowAndShrink
                .BackColor = NewColor
                .CellBorderStyle = TableLayoutPanelCellBorderStyle.Single
                .ColumnStyles.Add(New ColumnStyle(SizeType.AutoSize))
                .Location = New Point(528, 10)
                For I As Integer = 0 To dtParentTbls.Rows.Count - 1
                    strName = dtParentTbls.Rows(I).Item(0).ToString
                    schemaTbl = objOleDB.Get_OleDB_Tbl_Schema(cnnOleDB, strName)
    
                    Dim lblCtrl1 As New Label
                    lblCtrl1 = Ins_Label_Ctrl(lblCtrl1, "ValueMember-" & strName, "ValueMember-" & strName)
                    .Controls.Add(lblCtrl1, 1, I)
    
                    Dim cboBox1 As New ComboBox
                    cboBox1 = Ins_CboBox_Ctrl(cboBox1, "VM_" & strName, schemaTbl)
                    .Controls.Add(cboBox1, 2, I)
    
                    Dim lblCtrl2 As New Label
                    lblCtrl2 = Ins_Label_Ctrl(lblCtrl2, "DisplayMember-" & strName, "DisplayMember-" & strName)
                    .Controls.Add(lblCtrl2, 3, I)
    
                    Dim cboBox2 As New ComboBox
                    cboBox2 = Ins_CboBox_Ctrl(cboBox2, "DM_" & strName, schemaTbl)
                    .Controls.Add(cboBox2, 4, I)
                Next I
                .ResumeLayout()
            End With
            Return tlpCtrl
        End Function
    
        Private Sub cboBox_SelectedIndexChanged(sender As Object, e As EventArgs)
            Dim cbx As ComboBox = CType(sender, ComboBox) 'cast the 'sender' to a ComboBox to get a reference to the one that raised this event
            MessageBox.Show("You selected something in " & cbx.Name) 'just to show you can use the combobox's Name or other property to identify which one it is
        End Sub
    
        Private Function Ins_CboBox_Ctrl(ctrlCboBox As ComboBox, strName As String, dtaSource As DataTable) As ComboBox 'return a ComboBox, instead of a Control
            With ctrlCboBox
                AddHandler .SelectedIndexChanged, AddressOf cboBox_SelectedIndexChanged
                .Font = New Drawing.Font("Comic Sans MS", 10, FontStyle.Regular)
                .Size = New Size(120, 100) 'this needs to be a new Size, not a new Point
                .DataSource = dtaSource
                .DisplayMember = dtaSource.Columns(0).ToString
            End With
            Return ctrlCboBox
        End Function

     

     You should also be aware that when you add a handler to a control,  if you later want to dispose of the control,  you should use the RemoveHandler method on these controls.

     

     On another note,  The TableLayoutPanel and the ComboBox controls are Reference types so,  you don't really need a Function to return the same control you passed in.  You can simply use a Sub and pass them in.  For example,  the below Subs would do the same thing  as your Functions do but,  with a little less code.

        Private Sub Create_TLPCboBoxes(tlpCtrl As TableLayoutPanel, dtParentTbls As DataTable)
            Dim strName As String
            Dim schemaTbl As New DataTable
    
            With tlpCtrl
                .SuspendLayout()
                .Controls.Clear()
                .AutoSize = True
                .AutoSizeMode = AutoSizeMode.GrowAndShrink
                .BackColor = NewColor
                .CellBorderStyle = TableLayoutPanelCellBorderStyle.Single
                .ColumnStyles.Add(New ColumnStyle(SizeType.AutoSize))
                .Location = New Point(528, 10)
                For I As Integer = 0 To dtParentTbls.Rows.Count - 1
                    strName = dtParentTbls.Rows(I).Item(0).ToString
                    schemaTbl = objOleDB.Get_OleDB_Tbl_Schema(cnnOleDB, strName)
    
                    Dim lblCtrl1 As New Label
                    lblCtrl1 = Ins_Label_Ctrl(lblCtrl1, "ValueMember-" & strName, "ValueMember-" & strName)
                    .Controls.Add(lblCtrl1, 1, I)
    
                    Dim cboBox1 As New ComboBox
                    Ins_CboBox_Ctrl(cboBox1, "VM_" & strName, schemaTbl)
                    .Controls.Add(cboBox1, 2, I)
    
                    Dim lblCtrl2 As New Label
                    lblCtrl2 = Ins_Label_Ctrl(lblCtrl2, "DisplayMember-" & strName, "DisplayMember-" & strName)
                    .Controls.Add(lblCtrl2, 3, I)
    
                    Dim cboBox2 As New ComboBox
                    Ins_CboBox_Ctrl(cboBox2, "DM_" & strName, schemaTbl)
                    .Controls.Add(cboBox2, 4, I)
                Next I
                .ResumeLayout()
            End With
        End Sub
    
        Private Sub cboBox_SelectedIndexChanged(sender As Object, e As EventArgs)
            Dim cbx As ComboBox = CType(sender, ComboBox) 'cast the 'sender' to a ComboBox to get a reference to the one that raised this event
            MessageBox.Show("You selected something in " & cbx.Name) 'just to show you can use the combobox's Name or other property to identify which one it is
        End Sub
    
        Private Sub Ins_CboBox_Ctrl(ctrlCboBox As ComboBox, strName As String, dtaSource As DataTable)
            With ctrlCboBox
                AddHandler .SelectedIndexChanged, AddressOf cboBox_SelectedIndexChanged
                .Font = New Drawing.Font("Comic Sans MS", 10, FontStyle.Regular)
                .Size = New Size(120, 100) 'this needs to be a new Size, not a new Point
                .DataSource = dtaSource
                .DisplayMember = dtaSource.Columns(0).ToString
            End With
        End Sub
    


    If you say it can`t be done then i`ll try it

    • Edited by IronRazerz Monday, February 18, 2019 6:21 PM
    Monday, February 18, 2019 5:04 PM
  • Hello,

    If you do use a C# - I would recommend to overload a ComboBox and add specific code into new component.
    But you use VB and traditionally didn't follow this approach. 
    So, it's will be what Karen recommended or you can study inheritance in VB.


    Sincerely, Highly skilled coding monkey.

    Monday, February 18, 2019 5:21 PM
  • I think I need to clarify this problem.

    As previously stated: My application looks at the construction of a database's table and dynamically creates a data entry form.

    Currently, it creates the form like this:

    This particular form has two foreign keys outlined in green; CountryID and StateID. What I am trying to do is place a combo box at each foreign key column. Like so:

    In order to make the combo boxes function; I need to tell the combo boxes which column is the ValueMember and which is the DisplayMember for each foreign key column.

    My first idea was to show the Relationships(Parent Tables) needed for the selected table tblCustomers.

    These combo boxes are also dynamically generated. The purpose would allow the user to select the ValueMember and DisplayMember columns from the parent tables.

    So going back to the data entry form. We select from the parent table tblCountries; the column CountryID as the ValueMember and for the DisplayMember we select column Country . We do the same thing for the parent table tblStates.

    The information for both ValueMember and DisplayMember must go into string variables, so it can be used in the dynamically created combo boxes on the data entry form tblCustomers.

    Here is where the confusion starts.

    From ValueMember-tblCountries combo box. I need to set a string variable to "CountryID" and from DisplayMember- tblCountries combo box another to "Country" to make the CountryID column in the tblCustomers functional. This also needs to be done for the ValueMember-tblStates and DisplayMember-tblStates combo box to make the StateID column functional.

    I then need to take these variables and use them in the AddHandler functions in the dynamically created tblCustomers form to make the columns CountryID and StateID functional.

    Can this be done?

    Thanks,


    MRM256

    Wednesday, February 20, 2019 7:04 PM