Automatically Filtering a ComboBox in WPF

Haven't you always wanted to use a combo box like the one in Start menu's Run dialog?

afcb

A client asked me for the exact same thing, and now you can download it from here too. The Automatically Filtered ComboBox inherits from the original ComboBox and adds the auto-filtering, along with an ability to filter while ignoring case. The IsTextSearchEnabled property already allows you to do all this (and the use of which is therefore supported), but it doesn't allow for case sensitivity and filtration of the results.

Interesting bits about the code:

/// <summary>
///
Gets the text box in charge of the editable portion of the combo box.
/// </summary>
protected TextBox EditableTextBox
{
get
{
return ((TextBox)base.GetTemplateChild("PART_EditableTextBox"));
}
}

The above section of code lets you access the text box that sits at the top of the combo box. We use this text box to get the selection (just like what we could get from a ComboBox in Windows Forms using the SelectedText property).

ICollectionView view = CollectionViewSource.GetDefaultView(newValue);
view.Filter += this.FilterPredicate;
ICollectionView view = CollectionViewSource.GetDefaultView(this.ItemsSource);
view.Refresh();

The above two piece of code use the CollectionViewSource class to create a filter on the default view of the ItemsSource. This mechanism is used internally by the original ComboBox to filter and/or sort the source of the items. I'm a bit baffled by why the WPF team would decide on only using a single, global view for the items' source, rather than a model like the one presented in ADO.NET using the DataView on DataTables. Thanks to Tomer for  the heads up on that one.

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);
}

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

The above is the predicate for filtering. As you can see, only items that start with the text already in the box are kept in the list (Yes, value.ToString() is not a correct implementation; I will change it in the future). Notice that if the selection is on the end of the text, we do not use that part for the search, since the very next character pressed will replace the selected text and we would like to show the possible results for such a keystroke.

As I am still in the process of getting used to WPF, there is still much to learn. I would gladly accept any comment you may have about the means to the end and also on my style and conventions. You always have something new to learn :)

15 Comments

  • this is cool, I like the code

  • Hi Sounds cool, but Im new to WPF and wondering how to implement this class with a combobox declared in XAML. thx in Advance

  • I doubt it very much that this can be done with XAML only and no code.

  • Do you have a WPF sample application that uses this customized control?

  • Sorry, no, but you can simply use it like any other combo box. Just replace the usual name with the control's name and voila. :)

  • FTA: "The IsTextSearchEnabled property already allows you to do all this (and the use of which is therefore supported), but it doesn't allow for case sensitivity and filtration of the results."

  • Great stuff :)

    This code appears to throw in the getter for EditableTextBox if you are in the VS designer.

    One possible workaround is to wrap the addition of the event handler and check to see if (this.EditableTextBox != null) first.

    Also, should we be using

    return Template.FindName("PART_EditableTextBox", this) as TextBox;

    instead of GetTemplateChild?

  • Hey casper,

    Thanks for the comment. It's been a while since I touched the code, so maybe there are a few things I have missed. I hope whomever decides to use the code reads your comments and tries your suggestions, at the moment, I'm too far from WPF development to answer :)

  • Not working though. Neither with GetTemplateChild nor with Template.FindName...

  • Hi,
    I used the autofiltercombobox given here but i am not able to display the filtered item list on a popup like you have shown in the snapshot above. Can you please tell me how i can display a popup below the combobox which contains a filtered list of items based on the text entered.

  • Casper & Archimed7592, make sure you have the IsEditable property set to true in your XAML otherwise the retrieval of the template will fail since there is no TextBox to get.

  • Regarding the ToString() thing, I used a bit of reflection to get the value of the property identified by DisplayMemberPath:

    string textValue = string.Empty;

    if (!string.IsNullOrEmpty(this.DisplayMemberPath))
    {
    Type t = value.GetType();
    var displayMemberPathProperty = t.GetProperty(this.DisplayMemberPath);
    if (displayMemberPathProperty != null)
    {
    textValue = displayMemberPathProperty.GetValue(value, null).ToString();
    }
    }

    if (textValue.Length == 0)
    textValue = value.ToString();

    return textValue.StartsWith(prefix, !this.IsCaseSensitive, CultureInfo.CurrentCulture);

  • Daniel,
    Since it's been well over a year since I stopped doing WPF work and changes jobs, I unfortunately can't find the solution itself to attach. Sorry.

  • Sabe alguien hacerlo para un listview.

  • "I'm a bit baffled by why the WPF team would decide on only using a single, global view for the items' source, rather than a model like the one presented in ADO.NET using the DataView on DataTables. Thanks to Tomer for the heads up on that one."

    You don't have to use the default view. You can create your own collection view, usually in xaml, and then bind to it. However, if you just bind directly to the collection, then the combo box will automitcally use the default view.

Comments have been disabled for this content.