Simple Related Object List Binding

I thought I’d post a quick how-to here based on some questions that came up in the forums like this one. The question is how do we relate two lists of data together so that when we select a “parent” object we can get automatic filtering on the related items in a Winform. For instance, one person in the forums was trying to put a list of states in a ListBox and when you selected one, a list of cities in that state would populate a CheckedListBox.


The answer lies in the BindingSource object. BindingSources do all of the heavy lifting for you when it comes to filtering and even sorting and editing. The key to using these though is we need to set up our object model correctly, in this case State and City, so that they are related properly.


In our example, we create two classes, State and City and set them up so that State has an internal list of Cities. Then you can use data binding on the controls by setting up two BindingSources that are related — this will manage the position and the contents of the related lists automatically.


So our State and City classes need to look like this:


Public Class State


    Private m_cities As New List(Of City)


 


    Sub New(ByVal state As String)


        m_State = state


    End Sub


 


    Private m_State As String


    Public Property State() As String


        Get


            Return m_State


        End Get


        Set(ByVal value As String)


            m_State = value


        End Set


    End Property


 


    Public ReadOnly Property Cities() As List(Of City)


        Get


            Return m_cities


        End Get


    End Property


 


    Public Overrides Function ToString() As String


        Return Me.State


    End Function


 


End Class


 


 


Public Class City


    Sub New(ByVal city As String)


        m_City = city


    End Sub


 


    Private m_City As String


    Public Property City() As String


        Get


            Return m_City


        End Get


        Set(ByVal value As String)


            m_City = value


        End Set


    End Property


 


    Public Overrides Function ToString() As String


        Return Me.City


    End Function



End
Class


Then we can set up our data and two BindingSources that we relate together. We do this by setting the CitiesBindingSource.DataSource property to the StateBindingSource and then setting its DataMember to the Cities property on the State class. Once that’s set up then we set the BindingSources to the DataSource property of our controls:


Private States As New List(Of State)


Private StatesBindingSource As New BindingSource


Private CitiesBindingSource As New BindingSource


 


 


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


 


    Dim ny As New State(“New York”)


    ny.Cities.Add(New City(“New York”))


    ny.Cities.Add(New City(“Rochester”))


 


    Dim vt As New State(“Vermont”)


    vt.Cities.Add(New City(“Rutland”))


    vt.Cities.Add(New City(“South Burlington”))


 


    Dim id As New State(“Idaho”)


    id.Cities.Add(New City(“St. Albans”))


 


    States.Add(ny)


    States.Add(vt)


    States.Add(id)


 


 


    Me.StatesBindingSource.DataSource = Me.States


    Me.CitiesBindingSource.DataSource = Me.StatesBindingSource


    Me.CitiesBindingSource.DataMember = “Cities”


    Me.ListBox1.DataSource = Me.StatesBindingSource


    Me.ListBox2.DataSource = Me.CitiesBindingSource


 


End Sub


Now all we need to do is move the position in the StatesBindingSource when the SelectedIndex changes in the first ListBox. This will automatically filter the list of cities for us:


Private Sub ListBox1_SelectedIndexChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles ListBox1.SelectedIndexChanged


 


    Me.StatesBindingSource.Position = Me.ListBox1.SelectedIndex


 


End Sub


This is really easy when you use list controls that have a Datasource property like a Combobox, ListBox or a DataGridView. However, the CheckedListBox does not have one so we have to fill the items a little more manually. We still use the CitiesBindingSource though because it will still contain our filtered list of items as the StatesBindingSource position changes. So we just need to add this code:


 


Private Sub ListBox1_SelectedIndexChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles ListBox1.SelectedIndexChanged


 


    Me.StatesBindingSource.Position = Me.ListBox1.SelectedIndex


 


    ‘Clear out the cities in the city checkedlistbox first


    CheckedListBox1.Items.Clear() 


   


    ‘This is necessary only because the CheckedListbox does not have


    ‘ a Datasource property. Any control with a Datasource property


    ‘ could simply be set to Me.CitiesBindingSource


    For Each c As City In Me.CitiesBindingSource.List


        Me.CheckedListBox1.Items.Add(c)


    Next


End Sub


So now when we move through our ListBox1, we will get automatic filtering on the rest of our controls.