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.