Alan Wayne
Alan Wayne

Reputation: 5384

Difficulty in defining the filter predicate for a custom Automatically Filtering ComboBox?

In attempting to work with the autofiltered combobox (Automatically Filtering a ComboBox in WPF, the new combobox control accepts the IEnumerable collection source with:

protected override void OnItemsSourceChanged(System.Collections.IEnumerable oldValue, System.Collections.IEnumerable newValue)
{
     if (newValue != null)
     {
          ICollectionView view = CollectionViewSource.GetDefaultView(newValue);
          view.Filter += this.FilterPredicate;
     }

     if (oldValue != null)
     {
           ICollectionView view = CollectionViewSource.GetDefaultView(oldValue);
           view.Filter -= this.FilterPredicate;
     }

     base.OnItemsSourceChanged(oldValue, newValue);
}

The IEnumerable newValue is defined in the viewmodel as:

public ObservableCollection<View_Small_Company> Companies

and bound to the control in the xaml as:

<wc:AutoFilteredComboBox  ItemsSource="{Binding PrimaryInsurance.Companies}"
                          ItemTemplate="{StaticResource CompanyTemplate}"
                          IsEditable="True"
                          IsTextSearchEnabled="True" 
                          TextSearch.TextPath="Companyname"
                          IsTextSearchCaseSensitive="False"               
                          Height="20" 
                          HorizontalAlignment="Left" 
                          Margin="162,235,0,0" 
                          VerticalAlignment="Top" 
                          Width="411"  />

My problem is in the predicate:

 private bool FilterPredicate(object value)
            {
                // We don't like nulls.
                if (value == null)
                    return false;

                // If there is no text, there's no reason to filter.
                if (this.Text.Length == 0)
                    return true;

                string prefix = this.Text;

                // If the end of the text is selected, do not mind it.
                if (this.length > 0 && this.start + this.length == this.Text.Length)
                {
                    prefix = prefix.Substring(0, this.start);
                }


                // The "value" is the Item being displayed. The Item being displayed can be styled in the xaml.
                // Example:
                //    ItemTemplate="{StaticResource CompanyTemplate}" uses the type Stargate_V.DataService.View_Small_Company 
                // so
                //    the "value.ToString() would give:  "Stargate_V.DataService.View_Small_Company" making StartsWith() useless.
                // value.ToString() will only work with string items.


                return value.ToString()
                    .StartsWith(prefix, !this.IsCaseSensitive, CultureInfo.CurrentCulture);
            }

As noted above, the "value" is returning a custom class -- not a string -- so the predicate fails. This control stands in a separate custom library, not related to the custom class that is being filtered.

So what is the best way to get the custom class of the value such that I can filter on value.Companyname ?

Or am I way off and there is a better way?

Upvotes: 0

Views: 473

Answers (2)

Alan Wayne
Alan Wayne

Reputation: 5384

This seems like a hack and I would much prefer @MarguilV suggestion (this is not the best answer). However, this seems like a simple solution: Override the ToString() property as:

public partial class View_Small_Company
{
    public override string ToString()
    {
        return Companyname;
    }
} 

Now the predicate within the control will work properly :)

Note: A far better way is answered in my next question How to bind a function to a dependency property. My thanks to tom.maruska for his help with this.

Upvotes: 0

Mark A. Donohoe
Mark A. Donohoe

Reputation: 30388

There's a few things I see that can be improved here. First, you don't account for cases where there's a selection in the middle of the string. I.e. 'ABCDE' is the string, but 'CD' is selected. Still, you can simply check the selection length, and if it's greater than zero, then do your substring from 0 to selection start.

Next, and more importantly, unless you're using a data path for the combo box, you shouldn't put the filter in the combo box itself for the very reason you mentioned: the items can be templated elsewhere meaning what's displayed is dictated by the template. That's why you can't use 'ToString' there. Instead, the proper thing to do is pass the predicate to the control rather than trying to have the control handle the predicate itself (which as you pointed out, can't be determined thanks to the data template.) That way, you as the author of the template know what you're displaying and can write the predicate accordingly.

TLDR version:

The consumer of the control should pass the predicate to the control since the control.

Hope that makes sense.

Upvotes: 1

Related Questions