Combobox filling and firing off events for other comboboxes

PatM

Well-known member
Joined
Dec 5, 2012
Messages
52
Programming Experience
10+
I've got four bound comboboxes (configured as dropdown lists). I'm filling each from a dataset (which is the easy part) but the second combo depends on the first being set and the third and fourth depend on the second being set. So what I have to do is

Fill #1 combo which is from an unfiltered bindingsource and set it to the first entry so that
Fill #2 combo from a bindingsource which is filtered on #1's selectedvalue
Fill #3 and #4 from a bindingsource which is filtered on #1's and #2's selectedvalues

Where I'm lost is which events to use to set the filter's and bindingsource positions for child combos...

I was messing with *ListChanged but that ends up firing every time something is added to the combobox. I can't seem to find anything that will trigger AFTER filling. Any suggestions?
 
You don't have to use any events. If you set up the bindings correctly then it's all done for you. Check this out:

Master/Detail (Parent/Child) Data-binding

That is written for two controls in a parent/child relationship. If you have four controls then its simply three pairs of controls in parent/child relationships, so you just have to do as instructed three times.
 
That's a great example, thanks! I may also fix my problem with doing bindingsources in code... Keep getting a *.*.*.datarowview showing up in the combobox instead of the assigned displaymember. Though if I assign the table as the datasource it displays the proper text. Have to have a good look at how you do the bindingsources.
 
Hmmm. That made it much simpler to set all the comboboxes but I'm still left with the problem of not knowing which event I should be using when the comboboxes change.

What I'm trying to do is get the datatable ID of the item in the listbox and populate a listview. If a user changes the combobox I get the SelectedIndexChanged event but I don't get that when the bindingsource changes the combobox contents.

If I could bind the listview to the bindingsource's current object that would be great but it appears you can't do that. As far as I can tell I can bind a table and use some sort of select query to home in on the right row but I'd still need to know when the combobox changes contents.
 
I see a lot of people using ListViews when they really shouldn't be. If you're using a ListView in Details view only and not using the grouping functionality then, for appearances, you won;t lose anything by using a DataGridView and, code-wise, you will gain. Most importantly for you, a DataGridView supports data-binding and can therefore be filtered automatically just as your ComboBoxes are.
 
I'm populating the listview with a single row and using the column captions and row value and listing them vertically (like the IDE properties windows). I'm going to see about grouping in a couple of them (I use 4 now and will probably have 10-12 by the time I'm done).

I discovered that a big problem I'm having is that the bound comboboxes don't propagate their changes as fast as a selectedindexchange code can run. If I use the parent's combobox SelectedIndexChanged and use a child's SelectedValue within it the child's SelectedValue is blank. I'm not sure if it's because the propagation is asynchronous and slower or synchronous and can't finish until the parent's SelectedIndexChanged sub finishes.

To get around that I set up several timers who's tick events call the routines to process the index changes (20ms delay) then have the index change events enable the appropriate timer (which is disabled in the associated tick). Then I just populate the listviews from the dataset manually.

Each listview populating sub calls it's child table's populate subs so it all just cascades through.

I'll post an abridged version tomorrow for anyone running into the same problems as me (or for people to see, point, laugh, and say "Hey! Do this!" 8)
 
I was actually looking at the bindingsource events before but got muddled up and went back to the control events. I've switched everything to the bindingsource currentchanged event and it's all working exactly as I want. No timers, no cascading subs, just currentchanged events.

I also moved the database populating from form_load to form_paint because I realized the controls didn't exist during load but they did at paint. That helped with the combobox.SelectedIndex being blank (the control didn't exist at load but did at paint kind of thing) though I had to use a boolean to make sure it only fired once.

I also stopped using anything from the comboboxes. I had been using selectedValue to do a .Select() but then I realized it's in the .Current and the .Current is always there. I just have to check for an empty bindingsource (I can have no rows in some of the child table bindingsources).

The only way you could have helped me more is to write the program for me but then I'd still be lost the next time I want to do something like this 8)
 
I also moved the database populating from form_load to form_paint because I realized the controls didn't exist during load but they did at paint. That helped with the combobox.SelectedIndex being blank (the control didn't exist at load but did at paint kind of thing) though I had to use a boolean to make sure it only fired once.
That's all wrong I'm afraid. You don't use the Paint event for anything but drawing. If you want to do something after the form has been displayed for the first time then you handle the Shown event. That said, the controls absolutely do exist when the Load event is raised. That is where you pretty much always do things like loading data. If there's an issue then I think it may be something other than what you think it is. It might be a good idea to describe the specific details here so we can get it sorted for you.
 
Well my description was poor. By not exist I actually meant the combobox position etc wasn't set yet. Anyway, here's an abridged version of what I'm doing. It's build on the ParentChild example code from the link above. Shouldn't need to do anything other than paste into a blank project.

edit: I had one question but it'd a DB specific one so I'll ask there.

P.S. My application only needs to display data, not edit it, thus the lack of any input handling for data.

VB.NET:
Public Class Form1

#Region "Declarations"

#Region "Database Declarations"

    Dim dsHomes As DataSet

#End Region

#Region "Control Declarations"
    Private WithEvents bsHomes As BindingSource = New BindingSource
    Private WithEvents bsOccupants As BindingSource = New BindingSource
    Private WithEvents bsVehicles As BindingSource = New BindingSource
    Private WithEvents bsPets As BindingSource = New BindingSource
    Private WithEvents cbHomes As ComboBox = New ComboBox
    Private WithEvents cbOccupants As ComboBox = New ComboBox
    Private WithEvents cbVehicles As ComboBox = New ComboBox
    Private WithEvents cbPets As ComboBox = New ComboBox
    Private lsvHomes As ListView = New ListView
    Private lsvOccupants As ListView = New ListView
    Private lsvVehicles As ListView = New ListView
    Private lsvPets As ListView = New ListView
#End Region

#End Region

#Region "Form Events"

    Private Sub Form1_Load(ByVal sender As Object, _
                           ByVal e As EventArgs) Handles MyBase.Load

        Draw_Controls()
        'Get the data.  The DataSet must contain a Parent table,
        'a Child table and a ParentChild relation between them
        dsHomes = Me.GetDataSet()

        'Bind the parent source to the parent table.
        'Specify table first or bindingsource.currentchanged
        'doesn't know the table and you get an exception when
        'filling the listview
        Me.bsHomes.DataMember = "Homes"
        Me.bsHomes.DataSource = dsHomes
        Me.bsHomes.Position = 0

        'Bind the child source to the relationship.
        Me.bsOccupants.DataMember = "ParentChild"
        Me.bsOccupants.DataSource = Me.bsHomes

        Me.bsVehicles.DataMember = "ChildChild1"
        Me.bsVehicles.DataSource = Me.bsOccupants

        Me.bsPets.DataMember = "ChildChild2"
        Me.bsPets.DataSource = Me.bsOccupants

        'Bind the parent control to the parent source.
        Me.cbHomes.DisplayMember = "Address"
        Me.cbHomes.ValueMember = "ID"
        Me.cbHomes.DataSource = Me.bsHomes

        'Bind the child control to the child source.
        Me.cbOccupants.DisplayMember = "Name"
        Me.cbOccupants.ValueMember = "ID"
        Me.cbOccupants.DataSource = Me.bsOccupants

        Me.cbVehicles.DisplayMember = "Type"
        Me.cbVehicles.ValueMember = "ID"
        Me.cbVehicles.DataSource = Me.bsVehicles

        Me.cbPets.DisplayMember = "Type"
        Me.cbPets.ValueMember = "ID"
        Me.cbPets.DataSource = Me.bsPets

    End Sub

#End Region

#Region "BindingSource Events"

    Private Sub bsParent_CurrentChanged(sender As Object, e As System.EventArgs) Handles bsHomes.CurrentChanged
        'theView As ListView, theCurrent As DataRowView, theTable As DataTable, intStart As Integer
        Fill_ListViews(lsvHomes, bsHomes.Current, dsHomes.Tables("Homes"), 1)
    End Sub

    Private Sub bsChild_CurrentChanged(sender As Object, e As System.EventArgs) Handles bsOccupants.CurrentChanged
        Fill_ListViews(lsvOccupants, bsOccupants.Current, dsHomes.Tables("Child"), 3)
    End Sub

    Private Sub bsVehicles_CurrentChanged(sender As Object, e As System.EventArgs) Handles bsVehicles.CurrentChanged
        Fill_ListViews(lsvVehicles, bsVehicles.Current, dsHomes.Tables("Vehicles"), 3)
    End Sub

    Private Sub bsPets_CurrentChanged(sender As Object, e As System.EventArgs) Handles bsPets.CurrentChanged
        Fill_ListViews(lsvPets, bsPets.Current, dsHomes.Tables("Pets"), 3)
    End Sub

#End Region

#Region "Database functions"

    Private Function GetDataSet() As DataSet
        Dim data As New DataSet
        Dim parent As DataTable = Me.GetParentTable()
        Dim child As DataTable = Me.GetChildTable()
        Dim childOfChild1 As DataTable = Me.GetChildOfChild1Table()
        Dim childOfChild2 As DataTable = Me.GetChildOfChild2Table()

        Try
            data.Tables.Add(parent)
            data.Tables.Add(child)
            data.Tables.Add(childOfChild1)
            data.Tables.Add(childOfChild2)

            'Add a relationship between the ID of the parent
            'table and the ParentID of the child table.
            'Note that adding a relation automagically adds Foreign Key constraint
            'and adding a Foreign Key automagically adds a Relation
            data.Relations.Add("ParentChild", _
                               parent.Columns("ID"), _
                               child.Columns("ParentID"))
            data.Relations.Add("ChildChild1", _
                               child.Columns("ID"), _
                               childOfChild1.Columns("ParentID"))
            data.Relations.Add("ChildChild2", _
                              child.Columns("ID"), _
                              childOfChild2.Columns("ParentID"))
        Catch ex As Exception
            MsgBox(ex.Message)
        End Try

        Return data
    End Function

    Private Function GetParentTable() As DataTable
        Dim table As New DataTable("Homes")

        With table.Columns
            .Add("ID", GetType(Integer))
            .Add("Address", GetType(String))
            .Add("Colour", GetType(String))
            .Add("Year", GetType(Integer))
        End With
        Try
            With table.Rows
                .Add(1, "100 1st Street", "Green", 1960)
                .Add(2, "110 1st Street", "White", 1996)
                .Add(3, "200 2nd Street", "Blue", 2012)
            End With
        Catch ex As Exception
            MsgBox(ex.Message)
        End Try

        Return table
    End Function

    Private Function GetChildTable() As DataTable
        Dim table As New DataTable("Child")

        With table.Columns
            .Add("ID", GetType(Integer))
            .Add("ParentID", GetType(Integer))
            .Add("Name", GetType(String))
            .Add("Age", GetType(Integer))
            .Add("Gender", GetType(String))
            .Add("Height", GetType(String))
            .Add("Hair", GetType(String))
        End With
        Try
            With table.Rows
                .Add(1, 1, "Frank", 48, "Male", "5ft 11in", "Brown")
                .Add(2, 1, "Silvia", 43, "Female", "5ft 4in", "Brown")
                .Add(3, 1, "Charles", 23, "Male", "6ft 2in", "Blonde")
                .Add(4, 2, "Tina", 24, "Female", "5ft 6in", "Red")
                .Add(5, 2, "Patrick", 5, "Male", "3Ft", "Sandy Brown")
                .Add(6, 2, "Taylor", 3, "Female", "2ft 4in", "Brown")
                .Add(7, 3, "Greg", 21, "Male", "5ft 11in", "Brown")
                .Add(8, 3, "Roger", 19, "Male", "5ft 11in", "Brown")
                .Add(9, 3, "Buddy", 19, "Male", "5ft 11in", "Brown")
            End With
        Catch ex As Exception
            MsgBox(ex.Message)
        End Try
        Return table
    End Function

    Private Function GetChildOfChild1Table() As DataTable
        Dim table As New DataTable("Vehicles")

        With table.Columns
            .Add("ID", GetType(Integer))
            .Add("ParentID", GetType(Integer))
            .Add("Type", GetType(String))
            .Add("Year", GetType(Integer))
            .Add("Desc", GetType(String))
        End With
        Try
            With table.Rows
                .Add(1, 1, "Car", 1996, "Ford")
                .Add(2, 1, "Truck", 2010, "Ford")
                .Add(3, 2, "Car", 2011, "Hyundai")
                .Add(4, 3, "Motorcycle", 2009, "Honda")
                .Add(5, 4, "Car", 2012, "Fiat")
                .Add(6, 4, "Bicycle", 1990)
                .Add(7, 4, "Snowmobile", 2005, "Polaris")
                .Add(8, 5, "Tricyle", 2008, "Male")
                .Add(9, 6, "None")
                .Add(10, 7, "None")
                .Add(11, 8, "None")
                .Add(12, 9, "None")
            End With
        Catch ex As Exception
            MsgBox(ex.Message)
        End Try
        Return table
    End Function

    Private Function GetChildOfChild2Table() As DataTable
        Dim table As New DataTable("Pets")

        With table.Columns
            .Add("ID", GetType(Integer))
            .Add("ParentID", GetType(Integer))
            .Add("Type", GetType(String))
            .Add("Name", GetType(String))
            .Add("Age", GetType(Integer))
            .Add("Colour", GetType(String))
        End With
        Try
            With table.Rows
                .Add(1, 1, "Cat", "Fluffy", 3, "Black")
                .Add(2, 2, "Parakeet", "Bill", 1, "Yellow & Green")
                .Add(3, 2, "Iguana", "Tripod", 4, "Green")
                .Add(4, 3, "None")
                .Add(5, 4, "Dog", "Zeus", 10, "Black")
                .Add(6, 5, "None")
                .Add(7, 6, "Cat", "KittyMoo", 5, "Black & White")
                .Add(8, 7, "None")
                .Add(9, 8, "None")
                .Add(10, 9, "None")
            End With
        Catch ex As Exception
            MsgBox(ex.Message)
        End Try
        Return table
    End Function

#End Region

#Region "Filling ListViews"

    Private Sub Fill_ListViews(theView As ListView, theCurrent As DataRowView, theTable As DataTable, intStart As Integer)

        Dim intCount As Integer
        Dim lsvStrings(2) As String
        Dim lsvItem As ListViewItem

        theView.Items.Clear()

        If Not IsNothing(theCurrent) Then
            Dim drFound() As DataRow = theTable.Select("ID = " & theCurrent("ID"))
            If UBound(drFound) > -1 Then
                For intCount = intStart To theTable.Columns.Count - 1
                    If Not IsDBNull(drFound(0).Item(intCount)) Then
                        lsvStrings(0) = drFound(0).Table.Columns(intCount).Caption
                        lsvStrings(1) = drFound(0).Item(intCount)
                        lsvItem = New ListViewItem(lsvStrings)
                        theView.Items.Add(lsvItem)
                    End If
                Next
            End If
        End If
    End Sub

#End Region

#Region "Layout"

    Private Sub Draw_Controls()

        Const conTop = 10
        Const conHeight = 21
        Const conWidth = 240
        Const conLSVHeight = 6
        Const conSpacing = 5

        Dim intLeft As Integer
        Dim intColWidths(2) As Integer

        intColWidths(0) = conWidth / 3
        intColWidths(1) = intColWidths(0) * 2

        intLeft = conSpacing
        With cbHomes
            .Top = conTop
            .Left = intLeft
            .Height = conHeight
            .Width = conWidth
            .DropDownStyle = ComboBoxStyle.DropDownList
        End With
        Me.Controls.Add(cbHomes)

        With lsvHomes
            .Top = conTop + conHeight + conSpacing
            .Left = intLeft
            .Height = conHeight * conLSVHeight
            .Width = conWidth
            .View = View.Details
            .GridLines = True
            .Scrollable = False
            .Columns.Add("Item", intColWidths(0))
            .Columns.Add("Value", intColWidths(1))
        End With
        Me.Controls.Add(lsvHomes)

        intLeft += conWidth + conSpacing

        With cbOccupants
            .Top = conTop
            .Left = intLeft
            .Height = conHeight
            .Width = conWidth
            .DropDownStyle = ComboBoxStyle.DropDownList
        End With
        Me.Controls.Add(cbOccupants)

        With lsvOccupants
            .Top = conTop + conHeight + conSpacing
            .Left = intLeft
            .Height = conHeight * conLSVHeight
            .Width = conWidth
            .View = View.Details
            .GridLines = True
            .Scrollable = False
            .Columns.Add("Item", intColWidths(0))
            .Columns.Add("Value", intColWidths(1))
        End With
        Me.Controls.Add(lsvOccupants)

        intLeft += conWidth + conSpacing

        With cbVehicles
            .Top = conTop
            .Left = intLeft
            .Height = conHeight
            .Width = conWidth
            .DropDownStyle = ComboBoxStyle.DropDownList
        End With
        Me.Controls.Add(cbVehicles)

        With lsvVehicles
            .Top = conTop + conHeight + conSpacing
            .Left = intLeft
            .Height = conHeight * conLSVHeight
            .Width = conWidth
            .View = View.Details
            .GridLines = True
            .Scrollable = False
            .Columns.Add("Item", intColWidths(0))
            .Columns.Add("Value", intColWidths(1))
        End With
        Me.Controls.Add(lsvVehicles)

        intLeft += conWidth + conSpacing

        With cbPets
            .Top = conTop
            .Left = intLeft
            .Height = conHeight
            .Width = conWidth
            .DropDownStyle = ComboBoxStyle.DropDownList
        End With
        Me.Controls.Add(cbPets)

        With lsvPets
            .Top = conTop + conHeight + conSpacing
            .Left = intLeft
            .Height = conHeight * conLSVHeight
            .Width = conWidth
            .View = View.Details
            .GridLines = True
            .Scrollable = False
            .Columns.Add("Item", intColWidths(0))
            .Columns.Add("Value", intColWidths(1))
        End With
        Me.Controls.Add(lsvPets)

        intLeft += conWidth + conSpacing + (Me.Width - Me.ClientSize.Width)
        Me.Width = intLeft



    End Sub

#End Region

End Class
 
Back
Top