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.