WPF Databinding with ObservableCollection to Label - wpf

I have a ObservableCollection which I need to bind to 2 labels, first to show count of items in the collection and second to show the sum of values.
First label is bound to collections count property and second label is bound directly to ObservableCollection with a convertor to calculate total of all items
XAML looks something like this
<Grid>
<ListBox Name="itemList" ItemsSource="{Binding DataList}"/>
<Label Name="lblcount" Content="{Binding DataList.Count}" />
<Label Name="lblTotal" Content="{Binding DataList, Converter={StaticResource calculateTotalConvertor}" />
</Grid>
My VM has a collection like this
ObservableCollection<int> data = new ObservableCollection<int>();
public ObservableCollection<int> DataList
{
get { return data; }
set { data = value; }
}
My convertor code is
public class CalculateTotalConvertor : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
ObservableCollection<int> collection = value as ObservableCollection<int>;
return collection.Sum();
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Issue is on adding new items in DataList, ListView and label showing count of items gets updated but "lblTotal" doesnt get updated with total count.
Basically how to force your binding to be evaluated on ObservableCollection changes ? How does it work directly for ListView or DataGrid but not for label ?
I know this problem can be solved by creating a property in VM to show total and raise property change when collection gets updated but is there is any better solution than that ?
Of-course this is simplified form of my actual problem, I dont have access to the ViewModel and the collection, its a third party control. I am creating a wrapper user control and have a relative binding with the view to its inner collection.

The other answers correctly explain why it is not updating. To force it to update you can change your converter to an IMultiValueConverter:
public class CalculateTotalConvertor : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
ObservableCollection<int> collection = values.FirstOrDefault() as ObservableCollection<int>;
return collection.Sum();
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Then change your binding to a MultiBinding which also pulls in the Count:
<Label Name="lblTotal">
<Label.Content>
<MultiBinding Converter="{StaticResource calculateTotalConvertor}">
<Binding Path="DataList"/>
<Binding Path="DataList.Count"/>
</MultiBinding>
</Label.Content>
</Label>
Now the second binding will notify that the binding needs to update when items are added or removed, but you can just ignore the count value and not use it.

Its not updating because its bound to DataList and DataList has not changed, The count label updates because its bound to DataList.Count which is updated when an item is added to the list.
The only way I can think of to update the Sum label is to notify the UI that the DataList has changed, but this will cause the ListBox to rebind the list and it will performace will be a lot more expensive than just having a property on your model update the Sum.
So I think the best option would be to use a property on your model to caculate the sum using the ObservableCollections CollectionChangedEvent or in the logic that adds items to the list

It works for ListView and DataGrid, because these are ItemsControls that listen to the ObservableCollection's CollectionChangedEvent, which is raised when the collection itself is changed by adding or removing items.
The Label on the other hand is a ContentControl that only listens to the PropertyChangedEvent. Since your DataList is the same ObservableCollection after the insertion as it was before, no events are raised.
Just saw your edit:
If you are creating a wrapping control, give the 3rd party control a name and hook up to its inner collection's CollectionChangedEvent from your control's code behind. That way you can still push update notifications to your wrapping view.
Go with the extra property, it will save you some code on the converter. From the code behind:
public partial class MainWindow : Window, INotifyPropertyChanged
{
ObservableCollection<int> _list = new ObservableCollection<int>();
int _sum = 0;
Random rnd = new Random();
public MainWindow()
{
DataList = new ObservableCollection<int>();
DataList.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(DataList_CollectionChanged);
DataContext = this;
InitializeComponent();
}
void DataList_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
foreach (object number in e.NewItems)
_sum += (int)number;
break;
case NotifyCollectionChangedAction.Remove:
foreach (object number in e.OldItems)
_sum -= (int)number;
break;
}
OnNotifyPropertyChanged("Sum");
}
public int Sum { get { return _sum; } }
public ObservableCollection<int> DataList { get; set; }
private void Add_Btn_Click(object sender, RoutedEventArgs e)
{
DataList.Add(rnd.Next(0, 256));
}
private void Remove_Btn_Click(object sender, RoutedEventArgs e)
{
if (DataList.Count == 0)
return;
DataList.RemoveAt(DataList.Count - 1);
}
public event PropertyChangedEventHandler PropertyChanged;
void OnNotifyPropertyChanged(string property)
{
if (PropertyChanged == null)
return;
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}

Related

Binding ObservableCollection<> to a TextBox

I have data comming back from web service in the form of a ObservableCollection<string> I want to bind the collection to a read-only TextBox so that the user can select and copy the data to the clipboard.
To get the collection bound to the Text property of the TextBox I created IValueConverter which converts the collection to a text string. This seems to work except that it only works once, it is as if the binding does not recognize subsequent changes to the Observable collection. Here is a simple application that reproduces the problem, just to confirm the binding is working correctly I also bind to a `ListBox'
Is this because the Text binding simple does not handle the change events of the collection?
One option would of course be for me to handle the collection changes and propogate those to a Text property that the TextBox is bound to, which is fine, but I would like to understand why what seemed to me to be an obvious solutions is not working as expected.
XAML
<Window x:Class="WpfTextBoxBinding.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfTextBoxBinding"
Title="MainWindow" Height="331" Width="402">
<StackPanel>
<StackPanel.Resources>
<local:EnumarableToTextConverter x:Key="EnumarableToTextConverter" />
</StackPanel.Resources>
<TextBox Text="{Binding TextLines, Mode=OneWay, Converter={StaticResource EnumarableToTextConverter}}" Height="100" />
<ListBox ItemsSource="{Binding TextLines}" Height="100" />
<Button Click="Button_Click" Content="Add Line" />
</StackPanel >
</Window>
Code Behind
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Text;
using System.Windows;
using System.Windows.Data;
using System.Globalization;
namespace WpfTextBoxBinding
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public ObservableCollection<string> TextLines {get;set;}
public MainWindow()
{
DataContext = this;
TextLines = new ObservableCollection<string>();
// Add some initial data, this shows that the
// TextBox binding works the first time
TextLines.Add("First Line");
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
TextLines.Add("Line :" + TextLines.Count);
}
}
public class EnumarableToTextConverter : IValueConverter
{
public object Convert(
object value, Type targetType,
object parameter, CultureInfo culture)
{
if (value is IEnumerable)
{
StringBuilder sb = new StringBuilder();
foreach (var s in value as IEnumerable)
{
sb.AppendLine(s.ToString());
}
return sb.ToString();
}
return string.Empty;
}
public object ConvertBack(
object value, Type targetType,
object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
A slightly more elegant way to achieve that is to use MultiBinding on the Text property and bind to the Collection's Count property. This will update the binding every time the collection's Count changes and update the Text according to a MultiValueConverter you define.
<TextBox>
<TextBox.Text>
<MultiBinding Converter="{x:Static l:Converters.LogEntryCollectionToTextConverter}">
<Binding Path="LogEntries" Mode="OneWay"/>
<Binding Path="LogEntries.Count" Mode="OneWay" />
</MultiBinding>
</TextBox.Text>
</TextBox>
And the converter:
public static class Converters
{
public static LogEntryCollectionToTextConverter LogEntryCollectionToTextConverter = new LogEntryCollectionToTextConverter();
}
public class LogEntryCollectionToTextConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
ObservableCollection<LogEntry> logEntries = values[0] as ObservableCollection<LogEntry>;
if (logEntries != null && logEntries.Count > 0)
return logEntries.ToString();
else
return String.Empty;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
In my use case, I don't allow the TextBox to update its source (hence the ´Mode="OneWay"´), but if need be the Converter's ConvertBack method would handle that.
Is this because the Text binding
simple does not handle the change
events of the collection?
Indeed. A binding updates only when its source property changes. If you change the TextLines property by setting a whole new ObservableCollection and implement INotifyPropertyChanged, your binding will work as expected. Adding new elements to the collection will have meaning only if it's bound to a property like ItemsControl.ItemsSource that listens to the collection changes.
One option would of course be for me
to handle the collection changes and
propogate those to a Text property
that the TextBox is bound to, which is
fine.
That would be another solution.
update below code
private void Button_Click(object sender, RoutedEventArgs e)
{
TextLines.Add("Line :" + TextLines.Count);
BindingExpression be = BindingOperations.GetBindingExpression(txtName, TextBox.TextProperty);
be.UpdateTarget();
}
where txtName is your name of your textbox
MVVM way
1- Difine a property of type string in your ViewModel as shown below and bind this property to the textbox text property a shown below and remove ValueConverter no need now.
public string TextLines {get;set;}
<TextBox Text="{Binding TextLines, Mode=OneWay/>
2- I think , you most probably handling button click event using a Command Handler say your Command is AddMoreLines
so in the AddMoreLine Command Handler , after adding a new object in your OBservrableCollection , create a StringBuilder and append all the content of your Collection and assign the string to the property created in step 1.
3- Call PropertyChanged Handler.

Silverlight binding in a DataGridRowGroupHeader doesn't update when DataSource changes

I have a Binding in a inline style for the DataGridRowGroupHeader like this.
<sdk:DataGrid.RowGroupHeaderStyles>
<Style TargetType="sdk:DataGridRowGroupHeader">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="sdk:DataGridRowGroupHeader">
<TextBlock Margin="4,0,0,0" Text="{Binding Converter={StaticResource headerConverter}}" />
The DataGrid ItemsSource is Bound to a PageCollectionView containing an observable collection, which is grouped by a property in the collection. When I update the collection the rows of the grid change, but the binding in the GroupHeader does not change.
Is there a different way to bind this or a way to force the UI to update?
This is the converter I'm using on the Header binding:
public class GroupHeaderConverter2 : IValueConverter {
public object Convert(object value, System.Type targetType, object parameter, CultureInfo culture) {
var cvg = value as CollectionViewGroup;
return string.Format("({0} Remaining)", cvg.Items.Count((i) => ((CheckListEventDefinition)i).Complete == false && ((CheckListEventDefinition)i).Required == true));
}
public object ConvertBack(object value,
System.Type targetType,
object parameter,
CultureInfo culture) {
return null;
}
}
Got this to work by changing the source collection to my own extended ObservableCollection that also monitors the elements for PropertyChanged and then raises the CollectionChanged Event.
/// <summary> this collection is also monitoring the elements for changes so when PropertyChanged fires on any element, it will raise the CollectionChanged event</summary>
public class ObservableCollectionEx<T> : ObservableCollection<T> where T : INotifyPropertyChanged {
public ObservableCollectionEx(ObservableCollection<T> regularCollection) {
if (regularCollection != null) {
foreach (var item in regularCollection) {
this.Add(item);
}
}
}
public void RaiseCollectionChanged() {
this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e) {
Unsubscribe(e.OldItems);
Subscribe(e.NewItems);
base.OnCollectionChanged(e);
}
protected override void ClearItems() {
foreach (T element in this)
element.PropertyChanged -= handlePropertyChanged;
base.ClearItems();
}
private void Subscribe(IList iList) {
if (iList == null) return;
foreach (T element in iList)
element.PropertyChanged += handlePropertyChanged;
}
private void Unsubscribe(IList iList) {
if (iList == null) return;
foreach (T element in iList)
element.PropertyChanged -= handlePropertyChanged;
}
private void handlePropertyChanged(object sender, PropertyChangedEventArgs e) {
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
}
The problem is you are binding directly to the object and not to a property with a Path. If you bind without a Path the binding will never update because there is no PropertyChanged event to notify the UI that the binding has changed. The simplest change is to change your binding to {Binding Items, Converter={StaticResource headerConverter}} and then cast value in your converter directly to ReadOnlyObservableCollection<object>.
If you need more flexibility then this I believe you will have to implement your own ICollectionView with a custom CollectionViewGroup.
Stephan
I follow your solution with a difference:
- i use a parameter
- i use a datagridcell
but when i modify my datasource the datagroupheader doesn't change.
<data:DataGridCell Content="{Binding Items,Converter={StaticResource myConverterBnc}, ConverterParameter=Fattura,UpdateSourceTrigger=PropertyChanged}" Foreground="White" Width="113"/>

Converter stops filter working

I am trying to display filenames in a listbox, retrieved from a particular directory. They are stored in an ObservableCollection of FileInfo objects:
public ObservableCollection<FileInfo> ProjectFiles
{
get
{
if (SelectedFolder == null) return null;
DirectoryInfo d= new DirectoryInfo(SelectedFolder);
if (!d.Exists) return null;
return new ObservableCollection<FileInfo>(d.EnumerateFiles("*.xsi"));
}
}
I have implemented a filter on the listbox, called when text is entered or changed in a textbox "FilesFilterBy":
private void FilterFiles_TextChanged(object sender, TextChangedEventArgs e)
{
ICollectionView view = CollectionViewSource.GetDefaultView(ProjectFiles);
view.Filter = new Predicate<object>(IsTextInFilename);
}
public bool IsTextInFilename(object item)
{
string Filename = Path.GetFileNameWithoutExtension((item as FileInfo).Name);
return (Filename.ToLower().Contains(FilesFilterBy.Text.ToLower()));
}
At the same time, I want to display only the names of the files, without path or extension. To this end I have implemented a converter:
public class RemoveExtensionConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return Path.GetFileNameWithoutExtension(value as string);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return new NotImplementedException();
}
}
Here is how the listbox is implemented in XAML:
<Window.Resources>
<ctr:RemoveExtensionConverter x:Key="JustFileName" />
</Window.Resources>
<ListBox ItemsSource="{Binding ProjectFiles}" >
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding FullName, Converter={StaticResource JustFileName}}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Currently the converter works - only the file names are listed, but the filter no longer has any effect. When I enter text in the FileFilterBy textbox the TextChanged event is fired but the listbox stays the same. Also, the converter is not called at that point.
What am I doing wrong?
ProjectFiles returns a new collection every time. Your FilterFiles_TextChanged handler is calling ProjectFiles to create a new collection, setting a filter on that new collection, and then throwing it away. The collection bound to the ListBox is not affected. You need to change ProjectFiles to keep the same collection object. Maybe something like this:
private ObservableCollection<FileInfo> _projectFiles;
public ObservableCollection<FileInfo> ProjectFiles
{
get
{
if (_projectFiles == null)
{
if (SelectedFolder == null) return null;
DirectoryInfo d = new DirectoryInfo(SelectedFolder);
if (!d.Exists) return null;
_projectFiles = new ObservableCollection<FileInfo>(
d.EnumerateFiles("*.xsi"));
}
return _projectFiles;
}
}
The Converter shouldn't affect the filter at all.

Bind a Wpf HierarchicalDataTemplate ItemsSource to a CollectionViewSource in a dictionary?

I'm trying to display a Wpf Treeview with items sorted by a CollectionViewSource.
Currently, everything is working except sorting using this code in my resource dictionary:
<HierarchicalDataTemplate DataType="{x:Type books:Container}" ItemsSource="{Binding Path=Items}">
<nav:ContainerControl />
</HierarchicalDataTemplate>
What would be the syntax for changing the HierarchicalDataTemplate to bind to a CollectionViewSource that in turn pulls from the Items property?
I've tried variations of the code posted on Bea Stollnitz's blog with no success. I can't figure out how to set the source of the CollectionViewSource.
Well let me just say that I hate my proposed solution, but it does work. Perhaps a WPF guru will enlighten us both with a better alternative. Of course if you were using a ViewModel behind your view, you could simply wrap the Items property of the model with a CollectionView in the ViewModel and be done with it.
But here's another solution. Basically, your HierarchicalDataTemplate can stay as is except you would add a Converter to the Binding. I implemented the following converter and changed the XAML accordingly.
<HierarchicalDataTemplate DataType="{x:Type books:Container}"
ItemsSource="{Binding Items, Converter={x:Static local:CollectionViewConverter.Instance}}">
<nav:ContainerControl />
</HierarchicalDataTemplate>
CollectionViewConverter.cs
public class CollectionViewConverter : IValueConverter
{
public CollectionViewConverter() {}
static CollectionViewConverter(){
Instance = new CollectionViewConverter();
}
public static CollectionViewConverter Instance {
get;
set;
}
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var view = new ListCollectionView((System.Collections.IList)value);
view.SortDescriptions.Add(new SortDescription("Name", ListSortDirection.Ascending));
return view;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
// not really necessary could just throw notsupportedexception
var view = (CollectionView)value;
return view.SourceCollection;
}
}
I did as you suggested and wrapped the Items collection with a ListCollectionView:
private SortDescription _ItemsLcvSortDesc;
private SortDescription ItemsLcvSortDesc
{
get
{
if (_ItemsLcvSortDesc == null)
_ItemsLcvSortDesc = new SortDescription("SortOrder", ListSortDirection.Ascending);
return _ItemsLcvSortDesc;
}
}
private ListCollectionView _ItemsLcv;
public ListCollectionView ItemsLcv
{
get
{
if (_ItemsLcv == null)
_ItemsLcv = CollectionViewSource.GetDefaultView(Items) as ListCollectionView;
_ItemsLcv.SortDescriptions.Add(ItemsLcvSortDesc);
return _ItemsLcv;
}
}
Did I miss anything?

Silverlight TabControl bound to ObservableCollection<string> not updating when collection changed

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();
}
}
}
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();
}
}
I realize this is a slightly old question at this point, but I don't know that anyone has explained why you need to do the INotifyPropertyChanged on the bound property on your view model.
The ItemsControl itself needs to be bound to an ObservableCollection for the collection change events to cause the ItemsControl to re-evaluate. Your converter is returning a distinct List (or Observable) collection each time it is called rather than holding on to a single ObservableCollection and adding items to it. Therefore, these collections never have any of the collection changed events raised on them... they're always new, each time the binding is re-done.
Raising PropertyChanged forces the binding to be re-evaluated and re-runs your converter, returning a distinct collection and reflecting your changes.
I feel a better approach may be to do the conversion in your ViewModel rather than in a Converter. Expose an ObservableCollection of TabItem that you bind directly to and that you modify in place. The TabControl should then see changes made directly to your collection without the need to raise PropertyChanged and re-evaluate the entire binding.
[Edit - Added my approach]
ViewModel:
public class TabSampleViewModel
{
private ObservableCollection _tabItems = new ObservableCollection();
public TabSampleViewModel()
{
AddTabItem("Alpba");
AddTabItem("Beta");
}
public ObservableCollection<TabItem> TabItems
{
get
{
return _tabItems;
}
}
public void AddTabItem( string newTabItemName )
{
TabItem newTabItem = new TabItem();
newTabItem.Header = newTabItemName;
newTabItem.Content = newTabItemName;
TabItems.Add( newTabItem );
}
}
View:
<controls:TabControl ItemsSource="{Binding TabItems}"/>
Expose
public ObservableCollection<TabItem> Classnames
{
get { return _classnames; }
set { _classnames = value; }
}
If you debug the valueconverter you'll see it's not being called as often as you think it is.
The problem could be that your ValueConverter returns a List<TabItem> instead of an ObservableCollection<TabItem>. Try that one line change and see if it helps.

Resources