Given a "customer" entity:
public class CustomerEntity: EntityBase
{
public Dictionary<int, string> ClientsOfCustomer = new Dictionary<int, string>();
public CustomerEntity()
{
// Load ClientsOfCustomer...
}
}
and two or more WPF ComboBoxes that have their ItemsSourceProperty
bound to the same source (say, an attribute of the above "Customer" entity):
var comboBox1 = new ComboBox();
var comboBox2 = new ComboBox();
comboBox1.SetBinding(ItemsControl.ItemsSourceProperty,
new Binding(itemsSourceProperty) { ElementName = "ClientsOfCustomer" });
comboBox2.SetBinding(ItemsControl.ItemsSourceProperty,
new Binding(itemsSourceProperty) { ElementName = "ClientsOfCustomer" });
The above is done before we actually have an instance of the underlying object. This happens sometime later in the application:
var customer = new CustomerEntity();
parentPageOfComboboxes.DataContext = customer;
if we then filter the ItemCollection
of either ComboBox
:
comboBox1.Items.Filter = i => ((KeyValuePair<int, string>)i).Value.StartsWith("a");
this ends up filtering the other ComboBox
as well.
I understand that this is because ComboBox
is a Selector
, which is an ItemsControl
, and ItemsControl
invokes ItemCollection.SetItemsSource
when the ItemsSource
DependencyProperty
changes. ItemCollection
then in turn makes use of CollectionViewSource.GetDefaultCollectionView( _itemsSource, ModelParent, GetSourceItem)
to grab the default CollectionView
associated with the given _itemsSource
from cache. Therefore if you set the Filter on the underlying ItemCollection
of one ComboBox
, you are actually setting it on the cached and shared CollectionView
associated with the ItemsSource
that the ComboBox
is bound to. I guess the WPF folks didn't expect anyone to want to do so - unfortunate. But what can I do about it?
I've found two similar questions on SO without any real answer: "Filtered Combobox ItemsSource binding issue" (which seems to be about a custom "FilteredComboBox") and "Wpf ListBoxes' ItemsSource strange behaviour (which involves listboxes without data binding).
I've also implemented a fairly ugly solution that I will include below as an answer. However, there must be a better way.
UPDATE/CLARIFICATION: I did not express myself clearly in my original question. The answer to this question is trivial in cases where we have access to the actual instance of the entity that we are binding to at the time of data binding. However, I have a complex application that generates forms "on-the-fly" from metadata (which can come from C# Attributes, or from meta-data stored in a database, it doesn't matter). In the other similar questions posed on SO, the actual binding source (the data context) is always available, so simply doing something like Items = new ListCollectionView(...);
(either in code or in XAML) is possible. But what if the actual DataContext
is only set somewhere much further down the application lifecycle based on metadata? What solution is there other than something very crude like looping through all generated UI elements and altering the binding source retroactively? I hope this clarification is understandable.