Silverlight TabControl bound to ObservableCollection<string> not updating when collection changed
data-binding, observablecollection, silverlight, tabcontrol
Solution
Update 8/19
The concise answer is you have to implement INotifyPropertyChanged on the view model and notify listeners when the Property/Collection is changed.
Implement INotifyPropertyChanged on the ViewModel
* implement the interface INotifyPropertyChanged
* define the event (public event PropertyChangedEventHandler PropertyChanged)
* subscribe to the CollectionChanged event (Classnames.CollectionChanged += ...)
* fire the event for listeners
Best,
/jhd
ViewModel update per above... ValueConverter now called on all changes to the Property/Collection
public class ViewModel : INotifyPropertyChanged
{
private readonly ObservableCollection<string> _classnames = new ObservableCollection<string>();
public ViewModel()
{
Classnames.CollectionChanged += Classnames_CollectionChanged;
}
public event PropertyChangedEventHandler PropertyChanged;
private void Classnames_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
NotifyPropertyChanged("Classnames");
}
private void NotifyPropertyChanged(string info)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
foreach (PropertyChangedEventHandler d in handler.GetInvocationList())
{
d(this, new PropertyChangedEventArgs(info));
}
}
}
public ObservableCollection<string> Classnames
{
get { return _classnames; }
}
}
The XAML binding...
<UserControl.Resources>
<local:ViewModel x:Key="TheViewModel"/>
<local:TabConverter x:Key="TabConverter" />
</UserControl.Resources>
<StackPanel DataContext="{StaticResource TheViewModel}">
<ListBox ItemsSource="{Binding Classnames}" />
<controls:TabControl x:Name="TheTabControl"
ItemsSource="{Binding Classnames, Converter={StaticResource TabConverter}, ConverterParameter={StaticResource TheViewModel}}"/>
<Button Click="Button_Click" Content="Change Classnames" />
</StackPanel>
The ValueConverter (basically unchanged
public class TabConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var source = value as ObservableCollection<string>;
if (source == null)
return null;
//also sorted out the binding syntax to pass the ViewModel as a parameter
var viewModel = parameter as ViewModel;
if (viewModel == null)
throw new ArgumentException("ConverterParameter must be ViewModel (e.g. ConverterParameter={StaticResource TheViewModel}");
var tabItems = new List<TabItem>();
foreach (string classname in source)
{
// real code dynamically loads controls by name
var tabItem = new TabItem
{
Header = "Tab " + classname,
Content = new Button {Content = "Content " + classname}
};
tabItems.Add(tabItem);
}
return tabItems;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Problem
Silverlight 3 app with a TabControl bound to an ObservableCollection using an IValueConverter. Initial the binding works (converter called) on app startup. Changes, Clear() or Add(), to the bound collection are not reflected in the TabControl... converter not called. note: the bound ListBox reflects the changes to the bound collection while the TabControl does not. Ideas? /jhd The XAML binding... ``` <UserControl.Resources> <local:ViewModel x:Key="TheViewModel"/> <local:TabConverter x:Key="TabConverter" /> </UserControl.Resources> <StackPanel DataContext="{StaticResource TheViewModel}"> <ListBox ItemsSource="{Binding Classnames}" /> <controls:TabControl x:Name="TheTabControl" ItemsSource="{Binding Classnames, Converter={StaticResource TabConverter}, ConverterParameter=SomeParameter}"/> <Button Click="Button_Click" Content="Change ObservableCollection" /> </StackPanel> ``` The ViewModel... ``` namespace DatabindingSpike { public class ViewModel { private ObservableCollection<string> _classnames = new ObservableCollection<string>(); public ViewModel() { _classnames.Add("default 1 of 2"); _classnames.Add("default 2 of 2"); } public ObservableCollection<string> Classnames { get { return _classnames; } set { _classnames = value; } } } } ``` The converter (for completeness)... ``` namespace DatabindingSpike { public class TabConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { var source = value as ObservableCollection<string>; if (source == null) return null; var param = parameter as string; if (string.IsNullOrEmpty(param) || param != "SomeParameter") throw new NotImplementedException("Null or unknow parameter pasased to the tab converter"); var tabItems = new List<TabItem>(); foreach (string classname in source) { var tabItem = new TabItem { Header = classname, Content = new Button {Content = classname} }; tabItems.Add(tabItem); } return tabItems; } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { throw new NotImplementedException(); } } } ```