1
votes

What I'm trying to do :
I have 2 comboboxes, a "regular" one, and a filtrable one. On the filtrable combobox ItemsSource is binded to a property of the first combobox SelectedItem.
Here is some XAML to demonstrate the relation (I removed some properties):

<ComboBox Name="cbx_poche" ItemsSource="{Binding DataContext.ListePoches, ElementName=Main}" SelectedItem="{Binding PocheCible}" />
<controls:FilteredComboBox ItemsSource="{Binding SelectedItem.SupportsEligibles, ElementName=cbx_poche}" SelectedItem="{Binding Support}" />

The FilteredComboBox is a derived class from ComboBox inspired by those articles : Building a Filtered ComboBox for WPF / WPF auto-filtering combo.

The user can type in the combobox and it filters the list to display the items matching. The default behavior of the combobox is NOT desired (it automatically completes what the user types), this is why it's derived.

Those comboboxes above are in a ItemsControl element, because I need to have one row for each item in a specific collection. The SelectedItem properties of the comboboxes are binded to the item in this collection.

The result :
Combobox demo

The problem :
It works quite well... as long as you don't select the same item in the first combobox (like in the example above : if I type some text that doesn't match the above combo, it will be reset).
As soon as several FilteredComboBox are linked to the same item in the first combobox (so binded to SelectedItem.SupportsEligibles), typing text in the FilteredComboBox filters both lists.

I know why it does that, I don't know how to fix it. So I tried two things :

Code 1 (current code) : The problem is that the code uses the default view on the list, so all controls binded to this list will apply the same filter :

protected override void OnItemsSourceChanged(IEnumerable oldValue, IEnumerable newValue)
{
    if (newValue != null)
    {
        if (ItemsSourceView != null)
            ItemsSourceView.Filter -= this.FilterPredicate;

        ItemsSourceView = CollectionViewSource.GetDefaultView(newValue);
        ItemsSourceView.Filter += this.FilterPredicate;
    }

    base.OnItemsSourceChanged(oldValue, newValue);
}

Code 2 : So my (naïve) idea was to get a local view from the binded view. I works well for filtering, but breaks the binding (changing the selected item in the first combo doesn't update the list after the first pass)

protected override void OnItemsSourceChanged(IEnumerable oldValue, IEnumerable newValue)
{
    if (newValue != null && newValue != ItemsSourceView)
    {
        if (ItemsSourceView != null)
            ItemsSourceView.Filter -= this.FilterPredicate;

        ItemsCollectionViewSource = new CollectionViewSource { Source = newValue };
        ItemsSourceView = ItemsCollectionViewSource.View;
        ItemsSourceView.Filter += this.FilterPredicate;
        this.ItemsSource = ItemsSourceView; // Breaks the binding !!
    }

    base.OnItemsSourceChanged(oldValue, newValue);
}

I'm stuck here.
I'm looking for some event or Binding class that I could use to be notified of the binding change, in order to update the view. Or maybe getting the view applied without having to change the ItemsSource

1

1 Answers

0
votes

I ended up using a quite lame workaround, so I'm still interested in smart answers.

For people interested : I added another similar ItemsSource2 Dependency Property (still didn't find a nice name for it) on which I bind my item list, instead of the original ItemsSource.

When this items source is changed, the control gets a new CollectionView (not the default one) and sets it to the "standard" ItemsSource.
The other elements of the control remains identical (similar to the code in the linked articles).

public static readonly DependencyProperty ItemsSource2Property =
   DependencyProperty.Register(
   "ItemsSource2",
   typeof(IEnumerable),
   typeof(FilteredComboBox),
   new UIPropertyMetadata((IEnumerable)null, new PropertyChangedCallback(OnItemsSource2Changed)));

[Bindable(true)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public IEnumerable ItemsSource2
{
    get { return (IEnumerable)GetValue(ItemsSource2Property); }
    set
    {
        if (value == null)
        {
            ClearValue(ItemsSource2Property);
        }
        else
        {
            SetValue(ItemsSource2Property, value);
        }
    }
}

private static void OnItemsSource2Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    var ic = (FilteredComboBox)d;
    var oldValue = (IEnumerable)e.OldValue;
    var newValue = (IEnumerable)e.NewValue;

    if (newValue != null)
    {
        //Prevents the control to select the first item automatically
        ic.IsSynchronizedWithCurrentItem = false;

        var viewSource = new CollectionViewSource { Source = newValue };
        ic.ItemsSource = viewSource.View;
    }
    else
    {
        ic.ItemsSource = null;
    }
}