Change GridViewColumn text color base on condition - wpf

This is my GridViewColumn:
<GridViewColumn Width="180" Header="Status">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock x:Name="Txt" Text="{Binding Status}" Foreground="Yellow" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
The Status field is a property of my binding object and all i want to do is change this GridViewColumn color but this time base on condition:
I have another propgerty called StatusMessage which is simple enum:
public enum StatusMessage
{
InProcess,
Done,
Wait
}
So this enum property is changing all the time and for every value of this enum i want to define different color.
Is it possible ?
Edit
My View model class inherit from BaseObservableObject:
public class BaseObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
protected virtual void OnPropertyChanged<T>(Expression<Func<T>> raiser)
{
var propName = ((MemberExpression)raiser.Body).Member.Name;
OnPropertyChanged(propName);
}
protected bool Set<T>(ref T field, T value, [CallerMemberName] string name = null)
{
if (!EqualityComparer<T>.Default.Equals(field, value))
{
field = value;
OnPropertyChanged(name);
return true;
}
return false;
}
}
My properties:
public string Status
{
get { return _status; }
set
{
_status = value;
OnPropertyChanged();
}
}
public StatusMsg StatusMessage
{
get { return _statusMsg; }
set {
_statusMsg = value;
OnPropertyChanged();
}
}
XAML:
<GridViewColumn Width="180" Header="Status" DisplayMemberBinding="{Binding Status}">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Foreground="{Binding StatusMsg,Converter={c:StatusMessageToColorConverter}}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
StatusMessageToColorConverter is the same as #grek40 suggested and still my TextBlock Foreground not changing.

I first focus on the value conversion, then I say something about the other requirement ("So this enum property is changing all the time and for every value of this enum i want to define different color.")
Since you have an enum value but you want to have a color specification, you need to convert the value. This can be done with the help of an implementation of the IConverter interface. There are different ways to reference a converter in XAML, I additionally inherit from MarkupExtension so I am able to access the converter directly.
public class StatusMessageToColorConverter : MarkupExtension, IValueConverter
{
// one way converter from enum StatusMessage to color
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value is StatusMessage && targetType == typeof(Brush))
{
switch ((StatusMessage)value)
{
case StatusMessage.InProcess:
return Brushes.Yellow;
case StatusMessage.Done:
return Brushes.Green;
case StatusMessage.Wait:
return Brushes.Red;
}
}
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
public StatusMessageToColorConverter()
{
}
// MarkupExtension implementation
public override object ProvideValue(IServiceProvider serviceProvider)
{
return this;
}
}
As you can see, I convert if the input value is of type StatusMessage and the target type is typeof(Brush), which is the Type of the Foreground property. I just chose some colors, you may use different colors or even more complex brushes if you like.
In XAML, the namespace of the converter needs to be referenced. In my case its just WpfApplication2 associated to the XAML namespace name c
<Window
[... other properties]
xmlns:c="clr-namespace:WpfApplication2">
Now, when binding to the foreground property, utilize the converter
<TextBlock Text="{Binding Status}" Foreground="{Binding StatusMessage,Converter={c:StatusMessageToColorConverter}}" />
This should do the trick.
Now to the other part about dynamically changing the value. Your viewmodel class needs to implement INotifyPropertyChanged and raise the PropertyChanged event whenever a value is changed. As an example, see the following class that only contains the two properties of your example and the necessary notification logic.
public class ItemModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
void NotifyPropertyChanged([CallerMemberName]string prop = null)
{
var tmp = PropertyChanged;
if (tmp != null) tmp(this, new PropertyChangedEventArgs(prop));
}
private string _Status;
public string Status
{
get { return _Status; }
set { _Status = value; NotifyPropertyChanged(); }
}
private StatusMessage _StatusMessage;
public StatusMessage StatusMessage
{
get { return _StatusMessage; }
set { _StatusMessage = value; NotifyPropertyChanged(); }
}
}
More complex viewmodels can follow the same approach. For less update overhead, compare the current value and the new value in the setter and only notify if the value actually changes.

In your view model you could add a property called GetColour that looks at the current enum value and returns a colour. Then just bind the GetColour property in your xaml.

Related

Bind to a property and Element value - WPF binding

I have a checkbox which is binded to a object's property "IsValidCustomer" and I have a listview that holds some customers.
Whenever My user selects any Customer in the list, I want the Checkbox Checked property to set to False that means my "IsValidCustomer" property also will set to False automatically. Is there any way of achieving this using WPF bindings?
Any help in this regard would be highly appriciated.
Regards
-Srikanth
First make sure that your view's Datacontext is set to a viewmodel that implements the INotifyPropertyChanged interface then add a SelectedCustomer property that will hold the selected Customer from the ListView,
Each time the SelectedCustomer is set, check its value and set the IsValidCustomer property
here the full code :
the View model
public class Customer
{
public String Name { get; set; }
public String Id { get; set; }
}
public partial class MainWindow : Window, INotifyPropertyChanged
{
private Customer _selectedCustomer;
public Customer SelectedCustomer
{
get
{
return _selectedCustomer;
}
set
{
if (_selectedCustomer == value)
{
return;
}
_selectedCustomer = value;
OnPropertyChanged();
IsValidCustomer = (_selectedCustomer == null);
}
}
private ObservableCollection<Customer> _listCustomers;
public ObservableCollection<Customer> ListCustomers
{
get
{
return _listCustomers;
}
set
{
if (_listCustomers == value)
{
return;
}
_listCustomers = value;
OnPropertyChanged();
}
}
private bool _isValidCustomer = false;
public bool IsValidCustomer
{
get
{
return _isValidCustomer;
}
set
{
if (_isValidCustomer == value)
{
return;
}
_isValidCustomer = value;
OnPropertyChanged();
}
}
public MainWindow()
{
InitializeComponent();
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
and the view
<StackPanel>
<CheckBox Content="IsValidCustomer" IsChecked="{Binding IsValidCustomer,Mode=TwoWay}"></CheckBox>
<ListView ItemsSource="{Binding ListCustomers}" SelectedItem="{Binding SelectedCustomer,Mode=TwoWay}"></ListView>
</StackPanel>
I am sure you have something like this in your model :
private bool _IsValidCustomer;
public bool IsValidCustomer
{
get { return _IsValidCustomer; }
set
{
_IsValidCustomer= value;
PropertyChanged(this, new PropertyChangedEventArgs("IsValidCustomer"));
}
}
Set the Binding for that bool property.
<Style TargetType="ListViewItem">
<Setter Property="IsSelected" Value="{Binding IsValidCustomer, Converter={StaticResource InverseBooleanConverter}}"/>
</Style>
Your CheckBox will be bound to this also :
<CheckBox IsChecked="{Binding IsValidCustomer, Mode=TwoWay}"/>
So, i assume you start with that IsValidCustomer set to true. And on selecting each row, you want to set it to false.
You will need an inverse boolean converter for this:
public class InverseBooleanConverter: IValueConverter
{
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
if (targetType != typeof(bool))
throw new InvalidOperationException("The target must be a boolean");
return !(bool)value;
}
public object ConvertBack(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
throw new NotSupportedException();
}
#endregion
}

Bind to certain item in ObservableCollection by checking value of string property

In my View Model (VM) I have an ObservableCollection of items. In my view I am binding to the collection. I have created a few user controls that have a dependency property that i am binding to called STCode. So for example a "Tag" object will have a "Name" property of type String and a "value" property of type integer.
In my ViewModel
Constructor
Tags.Add(new Tag("Tag1",111));
Tags.Add(new Tag("Tag2",222));
Tags.Add(new Tag("Tag3",333));
Tags.Add(new Tag("Tag4",444));
public ObservableCollection<Tag> Tags
{
get
{
return _TagList;
}
set
{
if (value != _TagList)
{
_TagList = value;
}
}
}
In my View
<my:UserControl1 x:Name="control1" Margin="12,89,0,0" HorizontalAlignment="Left" Width="257" Height="249" VerticalAlignment="Top" STCode="{Binding Path=Value}"/>
This will bind to the First items value property in the ObservableCollection (Showing "Tag1" value). Is there anyway that I can get a specific "tag" object from the observableCollection by specifying the string Name property? So basically if I had 3 instances of my usercontrol in the view, on each control I would like to specify the "Name" property of the Tag object as a string in XAML, and in return bind that specific control to that specific tags integer "Value" property?
I hope this makes sense
Model
public class Tag : ModelBase
{
private int _value;
public string Tagname { get; set; }
public int Value
{
get
{
return _value;
}
set
{
_value = value;
NotifyPropertyChanged("Value");
}
}
}
ModelBase
public class ModelBase :INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Since your UserControls are bound to the Collection itself and not the Item on the collection (the converter does this job internally) you must call PropertyChanged on the whole Collection when you want to refresh the bindings on your usercontrols.
Edit: Full solution
ViewModel:
public class MainWindowViewModel : INotifyPropertyChanged
{
public ObservableCollection<Tag> Tags { get; private set; }
public MainWindowViewModel()
{
Tags = new ObservableCollection<Tag>();
Tags.Add(new Tag("Tag1", 111));
Tags.Add(new Tag("Tag2", 222));
Tags.Add(new Tag("Tag3", 333));
Tags.Add(new Tag("Tag4", 444));
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
public void ChangeRandomTag()
{
var rand = new Random();
var tag = Tags[rand.Next(0, Tags.Count - 1)];
tag.Value = rand.Next(0, 1000);
OnPropertyChanged("Tags");
}
}
View XAML:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:wpfApplication1="clr-namespace:WpfApplication1"
Title="MainWindow"
Width="525"
Height="350">
<Window.Resources>
<wpfApplication1:MyConverter x:Key="MyConverter" />
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<ListBox ItemsSource="{Binding Tags}">
<ListBox.ItemTemplate>
<DataTemplate>
<Border Margin="1"
BorderBrush="Black"
BorderThickness="1"
CornerRadius="2">
<StackPanel Orientation="Vertical">
<TextBlock Text="{Binding Name}" />
<TextBlock Text="{Binding Value}" />
</StackPanel>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<StackPanel Grid.Column="1" Orientation="Vertical">
<Button x:Name="buttonChangeRandomTag"
Click="ButtonChangeRandomTag_OnClick"
Content="Change Random Tag Value" />
<TextBlock Text="{Binding Tags, Converter={StaticResource MyConverter}, ConverterParameter=Tag1}" />
<TextBlock Text="{Binding Tags, Converter={StaticResource MyConverter}, ConverterParameter=Tag2}" />
<TextBlock Text="{Binding Tags, Converter={StaticResource MyConverter}, ConverterParameter=Tag3}" />
<TextBlock Text="{Binding Tags, Converter={StaticResource MyConverter}, ConverterParameter=Tag4}" />
</StackPanel>
</Grid>
View Code behind:
public partial class MainWindow : Window
{
public MainWindow()
{
DataContext = new MainWindowViewModel();
InitializeComponent();
}
private void ButtonChangeRandomTag_OnClick(object sender, RoutedEventArgs e)
{
(DataContext as MainWindowViewModel).ChangeRandomTag();
}
}
Converter:
[ValueConversion(typeof(ObservableCollection<Tag>), typeof(int))]
public class MyConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var collection = value as ObservableCollection<Tag>;
var key = parameter as string;
if (collection == null || parameter == null)
return 0;
var result = collection.FirstOrDefault(item => item.Name.Equals(key));
if (result == null)
return 0;
return result.Value;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Tag Class:
public class Tag : INotifyPropertyChanged
{
private string name;
private int value;
public string Name
{
get { return name; }
set
{
if (value == name) return;
name = value;
OnPropertyChanged("Name");
}
}
public int Value
{
get { return value; }
set
{
if (value == this.value) return;
this.value = value;
OnPropertyChanged("Value");
}
}
public Tag(string name, int value)
{
Value = value;
Name = name;
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
You could use a ValueConverter to do that for you:
[ValueConversion(typeof(string), typeof(string))]
public class StringToTagPropertyConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value == null || value != typeof(ObservableCollection<Tag>)) return
DependencyProperty.UnsetValue;
if (parameter as string == null) return DependencyProperty.UnsetValue;
ObservableCollection<Tag> tagObject = (ObservableCollection<Tag>)value;
string returnValue = tagObject.Where(t => t.Name.ToLower() ==
parameter.ToString().ToLower()).FirstOrDefault();
return returnValue ?? DependencyProperty.UnsetValue;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return DependencyProperty.UnsetValue;
}
}
You would use it like this:
<my:UserControl1 x:Name="control1" Margin="12,89,0,0" HorizontalAlignment="Left"
Width="257" Height="249" VerticalAlignment="Top" STCode="{Binding Tags,
Converter={StaticResource StringToTagPropertyConverter},
ConverterParameter="Name"}" />
By changing the value of the ConverterParameter, you can get the ValueConverter to return different properties of your 'tag object'. I am assuming that you know how to add a value converter in XAML.

Unexpected red border (validation error) on DataGrid when selecting blank row

When I select (by clicking or by keyboard) blank row on my DataGrid (when I want to add new row), unexpected validation error occurs (but with no exception) - the border of datagrid changes to red color, as you can see on the image below. When I click second time on blank row, the red border dissapears. Everything other works fine, the new row is added. Besides, I don't have any validation rules. And when I make a row with empty text, value is valid.
I don't want this behavior and this red border, anybody knows, why this happens and how to fix it? Why and where some validation fails?
Below I append some source code:
DataGrid definition in xaml:
<DataGrid IsSynchronizedWithCurrentItem="True" DisplayMemberPath="Name"
ItemsSource="{Binding Path=ConfigFiles}" SelectedItem="{Binding Path=SelectedConfigFile}"
Grid.Column="1" Height="87" Margin="0,26,11,32" Style="{DynamicResource DataGridStyle}">
<DataGrid.Columns>
<DataGridTextColumn Width="1*" Binding="{Binding Name}" />
</DataGrid.Columns>
</DataGrid>
My ViewModel's part:
public class ManageModulesVM : BaseVM // Implements INotifyPropertyChanged
{
// ...
public ObservableCollection<ConfigFile> ConfigFiles
{
get { return selectedModule == null ? null : selectedModule.ConfigFiles; }
set
{
selectedModule.ConfigFiles = value;
OnPropertyChanged(() => ConfigFiles);
}
}
public ConfigFile SelectedConfigFile
{
get { return selectedModule == null ? null : selectedModule.SelectedConfigFile; }
set
{
if (value != null)
{
selectedModule.SelectedConfigFile = value;
}
OnPropertyChanged(() => SelectedConfigFile);
OnPropertyChanged(() => Parameters);
}
}
// ...
}
ConfigFile class:
public class ConfigFile
{
public string Name { get; set; }
public IList<Parameter> Parameters { get; set; }
public ConfigFile() { Name = ""; Parameters = new List<Parameter>(); }
}
Edit:
After further investigation I know, that SelectedItem Binding is causing problems (when I remove this binding, validation error stops to appear), but I still don't know why and how to fix this.
I've found my own solution to this question. I've written a value converter and tied it to the binding:
(SelectedItem="{Binding Path=SelectedConfigFile,Converter={StaticResource configFileConverter}}")
The converter class:
namespace Converters
{
public class SelectedConfigFileConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
if(value is ConfigFile)
return value;
return null;
}
}
}
Define resource in resources.xaml file (or in any other resources place):
<ResourceDictionary (...) xmlns:conv="clr-namespace:Converters" >
<conv:SelectedConfigFileConverter x:Key="configFileConverter" />
</ResourceDictionary>
The advantage of this solution is that the SelectedConfigFile property's type did't changed (to the general object type) so it is still strongly typed.
To get the reason, when you click the new row of DataGrid in Debug mode, please see the debug window. There are first exception messages which will give you the idea why your problem is occurred.
Yes, the problem is from type casting. You need to modify the type of SelectedItem to object type as below.
public class ManageModulesVM : BaseVM // Implements INotifyPropertyChanged
{
// ...
public object SelectedConfigFile
{
get { return selectedModule == null ? null : selectedModule.SelectedConfigFile; }
set
{
if (value != null)
{
selectedModule.SelectedConfigFile = value;
}
OnPropertyChanged(() => SelectedConfigFile);
OnPropertyChanged(() => Parameters);
}
}
// ...
}
Here's a general-purpose converter you can use for any DataGrid, binding any kind of item:
public class DataGridItemConverter : MarkupExtension, IValueConverter
{
static DataGridItemConverter converter;
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return (value != null && value.GetType() == targetType) ? value : null;
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
if (converter == null)
converter = new DataGridItemConverter();
return converter;
}
}
Since it implements MarkupExtension you don't even need to define a static resource, you can just reference it like this:
SelectedItem="{Binding SelectedThing,Converter={conv:DataGridItemConverter}}"
You can just add this line to your DataGrid:
<DataGrid Validation.ErrorTemplate="{x:Null}" />
You can just add this line to your DataGrid:
<DataGrid Validation.ErrorTemplate="{x:Null}" />
It will solve the problem

WPF Notify changes on object

I have a gridview were I define some columns, like this...
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding MyProp}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
I bind my gridview to a collection and implemts INotifyPropertyChanged in the property MyProp. This works well and any changes of MyProp are reflected to the gridview.
If I add another column that is bound to the object itself I dont get any notifications/updates. My code...
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={StaticResource myConverter}}"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
I think I need something like INotifyPropertyChanged for the object but I have no idea how to do this. Any suggestions?
Yes, the actual instance itself never changes - only its properties.
Presumably your converter relies on a bunch of properties from the object you've bound to? If so, you could use a MultiBinding and change your converter to an IMultiValueConverter. Then you can bind to all the dependent properties that might cause the TextBlock to update.
Make the object impletment the interface INotifyPropertyChanged
Here is an example from MSDN
public class DemoCustomer : INotifyPropertyChanged
{
// These fields hold the values for the public properties.
private Guid idValue = Guid.NewGuid();
private string customerName = String.Empty;
private string companyNameValue = String.Empty;
private string phoneNumberValue = String.Empty;
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
// The constructor is private to enforce the factory pattern.
private DemoCustomer()
{
customerName = "no data";
companyNameValue = "no data";
phoneNumberValue = "no data";
}
// This is the public factory method.
public static DemoCustomer CreateNewCustomer()
{
return new DemoCustomer();
}
// This property represents an ID, suitable
// for use as a primary key in a database.
public Guid ID
{
get
{
return this.idValue;
}
}
public string CompanyName
{
get {return this.companyNameValue;}
set
{
if (value != this.companyNameValue)
{
this.companyNameValue = value;
NotifyPropertyChanged("CompanyName");
}
}
}
public string PhoneNumber
{
get { return this.phoneNumberValue; }
set
{
if (value != this.phoneNumberValue)
{
this.phoneNumberValue = value;
NotifyPropertyChanged("PhoneNumber");
}
}
}
}

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