As I learn WPF I’m running into quite a few roadblocks, but I’m enjoying the fact that it’s a challenge and something new. One of the things I’m trying to get used to is Data Binding to the WPF UI. I’m also trying to learn WPF the MVVM way. There are a lot of examples on how to do this on the web so I’ll just get to what got me stuck for a bit on ComboBox items binding. Here’s some of my UI XAML and my code behind:
<DockPanel Margin="0" Height="42" Name="TopDockPanel" Width="auto" DockPanel.Dock="Top" LastChildFill="true">
<Grid Height="auto" Name="Grid1" Width="266" DockPanel.Dock="Left">
<Label Margin="7,7,0,9" HorizontalAlignment="Left" Width="71">Combo1:</Label>
<ComboBox ItemsSource="{Binding ComboList1}" Margin="84,9,22,13" SelectionChanged="Combo1_SelectionChanged" />
</Grid>
<Grid Height="auto" Name="Grid2" Width="281">
<Label HorizontalAlignment="Left" Margin="6,7,0,9" Width="89">Combo2:</Label>
<ComboBox ItemsSource="{Binding ComboList2}" Margin="101,9,20,13" IsEditable="False" />
</Grid>
<Grid Height="auto" Name="Grid3" Width="auto" />
</DockPanel>
Class SomeClass
Private _SomeClassVM As SomeClassViewModel = Nothing
Sub New()
' This call is required by the Windows Form Designer.
InitializeComponent()
' Add any initialization after the InitializeComponent() call.
_SomeClassVM = New SomeClassViewModel
Me.DataContext = _SomeClassVM
End SubPrivate Sub Combo1_SelectionChanged(ByVal sender As System.Object, ByVal e As System.Windows.Controls.SelectionChangedEventArgs)
_SomeClassVM.Combo1Changed(sender)
End Sub
End Class
Nothing complicated, a simple MVVMish implementation (ish because there’s event handler code in the code behind file, I’ll be refactoring this as I go on). My View Model implements the INotifyPropertyChanged Interface so that the UI can be notified of any property changes and update any data bindings as needed. My View Model also exposes a couple of Public Properties: ComboList1 and ComboList2 which are List (Of String) objects:
Public Class SomeClassViewModel
Implements INotifyPropertyChanged
Private _ComboList1 As List(Of String)
Private _ComboList2 As List(Of String)Public ReadOnly Property ComboList1() As List(Of String)
Get
Return _ComboList1
End Get
End Property
Public ReadOnly Property ComboList2() As List(Of String)
Get
Return _ComboList2
End Get
End Property
Sub New()
_ComboList1 = New List(Of String)
For Each item As String In ItemsList 'this is just a list of strings from anywhere
_ComboList1.Add(item)
Next
OnPropertyChanged("SQLServerList")
_ComboList2 = New List(Of String)
End Sub
Public Sub Combo1Changed(ByVal sender As System.Object)
_ComboList2.Clear()
For Each item As String In SomeOtherList 'this is just list of string from another place
_ComboList2.Add(sqlDatabase)
Next
OnPropertyChanged("ComboList2")
End Sub
Public Event PropertyChanged(ByVal sender As Object, ByVal e As System.ComponentModel.PropertyChangedEventArgs) Implements System.ComponentModel.INotifyPropertyChanged.PropertyChanged
Private Sub OnPropertyChanged(ByVal propertyName As String)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
End Sub
End Class
Now, I thought that clearing and adding items to my ComboList2 object would suffice in propagating change notifications up to the UI…WRONG. It seems there’s more to it than just clearing/adding to the list and raising the PropertyChanged event. This implementation only notified the UI that ComboList2 changed once at the first time I changed ComboBox1’s selected item instead of each time. What gives?
After some fiddling I found that I couldn’t use the current _ComboList2 object reference. I had to actually set the _ComboList2 object to a new List for the UI to update:
Public Sub Combo1Changed(ByVal sender As System.Object)
Dim NewList As New List(Of String)
For Each item As String In SomeOtherList 'this is just list of string from another place
NewList.Add(item)
Next
_ComboList2 = NewList
OnPropertyChanged("ComboList2")
End Sub
There’s a more elegant way to solve the issue I had, which is to use an ObservableCollection instead of List (Of String) objects:
Public Class SomeClassModel
Private _ComboList1 As ObservableCollection(Of String) = Nothing
Private _ComboList2 As ObservableCollection(Of String) = Nothing
Public ReadOnly Property ComboList1() As ObservableCollection(Of String)
Get
Return _ComboList1
End Get
End Property
Public ReadOnly Property ComboList2() As ObservableCollection(Of String)
Get
Return _ComboList2
End Get
End Property
Sub New()
_ComboList1 = New ObservableCollection(Of String)
For Each item As String In ItemsList 'this is just a list of strings from anywhere
_ComboList1.Add(item)
Next
_ComboList2 = New ObservableCollection(Of String)
End Sub
Public Sub Combo1Changed(ByVal sender As System.Object)
_ComboList2.Clear
For Each item As String In SomeOtherList 'this is just list of string from another place
_ComboList2.Add(item)
Next
End Sub
End Class
You’ll notice a couple of things. The first being that the View Model no longer needs INotifyPropertyChanged. This is because ObservableCollection implements INotifyPropertyChanged (and INotifyCollectionChanged). You’ll also notice that I was able to just clear/add to _ComboList2 without having to raise any events. The UI updated as expected and I suspect it’s because when clearing or adding items to the ObservableCollection, properties such as count raise the PropertyChanged event properly.