Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / WinForms

ComboBox with Suggestions Based on Loose Character Search

4.78/5 (34 votes)
26 May 2014LGPL34 min read 162.5K   4.5K  
A ComboBox whose suggestion list is based on loose character search

Introduction

I wanted to have a ComboBox with completion as powerful as the ones found in SublimeText or in Resharper.

As with the standard AutoComplete feature, the suggestion list is based on the items of the combo. But instead of displaying the items in which text begins with the search string, it displays the items in which text contains all the characters of the search string but not consecutively.

The list is updated as the search text changes and in each suggestion, the matched characters are displayed in bold.

Image 1

For example, in the screenshot above, "johns" is matched against:

  • Eyvind Johnson
  • Johannes Vilhelm Jensen
  • John Galsworthy
  • John Steinbeck
  • ...

Another available way of matching combo items against user input, is to match all the characters of the input string consecutively and to allow the wildcard '*'. This wildcard matches any sequence of characters.

Image 2

For example, in the screenshot above, "john*s" is matched against:

  • Eyvind Johnson
  • John Galsworthy
  • John Steinbeck
  • Saint-John Perse

A third available matching method simply use native .NET Regexs:

Image 3

For example, in the screenshot above, "john[^a]*s" is matched against:

  • Eyvind Johnson
  • John Steinbeck
  • Saint-John Perse

Background

I've borrowed a lot of code from the following two CodeProject articles:

Using the Code

This ComboBox can be used as any standard WinForm ComboBox. Just build the DLL and in the form designer, drag the combo from the Toolbox to your form.

The algorithm used for matching ComboBox items is set with the property MatchingMethod. It can take the values:

  • StringMatchingMethod.NoWildcards
  • StringMatchingMethod.UseWildcards
  • StringMatchingMethod.UseRegexs

The following properties should not be set:

  • DropDownStyle
  • AutoCompleteSource, AutoCompleteMode, ...

The maximum height of the dropdown is based on the property ItemHeight . Its width is based on the property DropDownWidth. Unless overridden, DropDownWidth is set to the width of the combo.

I recommend to use another font than the standard "Microsoft Sans Serif". There is no bold version of "Microsoft Sans Serif" and so the rendering of bold characters is quite poor. "Segoe UI" is a very good substitute.

Points of Interest

The EasyCompletionComboBox is a control derived from the standard ComboBox. Every time, the user modifies the contents of the edit control of the combobox, a custom dropdown is shown or updated.

The custom dropdown is derived from ToolStripDropDown and contains a standard ListBox.

The combo text is matched against all the items of the combo and a list of StringMatch is built. This list is used as the items of the dropdown's listbox.

C#
public class StringMatch
{
    /// <summary>
    /// The original source
    /// </summary>
    public string Text { get; private set; }
    /// <summary>
    /// The source decomposed on matches/non matches against the pattern
    /// </summary>
    internal List<string> Segments { get; private set; }
    /// <summary>
    /// Is the first segment a match?
    /// </summary>
    internal bool StartsOnMatch { get; private set; }
} 

As the search is done, the strings are split into segments. These segments are used when rendering the strings in the listbox for alternating bold and non-bold portions.

C#
private void onSuggestionListDrawItem(object sender, DrawItemEventArgs e)
{
    StringMatch sm = (StringMatch) m_suggestionList.Items[e.Index];
    e.DrawBackground();
    
    bool isBold = sm.StartsOnMatch;
    Rectangle rBounds = e.Bounds;
    foreach (string s in sm.Segments)
    {
        Font f = isBold ? m_boldFont : Font;
        DrawString(e.Graphics, e.ForeColor, ref rBounds, s, f);
        isBold = !isBold;
    }
    e.DrawFocusRectangle();
} 

The keyboard focus is set to the combo and not to the dropdown. So special keys like arrows or escape are translated into commands for the dropdown.

The automatic closing of the dropdown is a little tricky. I would have expected to use the AutoClose property of ToolStripDropDown, but unfortunately ToolStripManager steals the keyboard events when AutoClose is set. So an application IMessageFilter is set in order to detect when the user clicks outside the control and then the dropdown is closed.

History

  • 07 April 2014: First release
  • 09 April 2014: Version 1.0.1
    • Bug fix: When the "standard" dropdown was visible and the user has entered text in the edit box of the combo, this text was incorrectly replaced by something else
    • Bug fix: Incorrect test for the visibility of the dropdown
  • 13 April 2014: Version 1.0.2
    • Bug fix: Now the completion dropdown is only shown when the user enters text in the edit control of the combobox
  • 19 April 2014: Version 1.1.1
    • Bug fix: The tab key is now correctly handled
    • New feature: Added two ways of matching combo items against user input
      • Simple matching with '*' wildcards
      • Regex matching
  • 23 April 2014: Version 1.1.2
    • Bug fix: Fixed bug in the "Wildcard" string matching algorithm
  • 24 April 2014: Version 1.1.3
    • Bug Fix: Completely rewritten the "Wildcard" string matching algorithm
  • 26 May 2014: Version 1.1.4
    • Bug Fix: Better handling of when to show the completion list
  • 8 June 2014: version 1.1.5
    • Bug Fix: Uploaded incorrect version of wildcard completion in previous release
    • Bug Fix: Correct handling of clicking in non-client areas of the suggestion list box
    • Bug Fix: Ampersands are now correctly displayed
    • Bug Fix: The suggestion list box now uses the control font
    • Thanks to omzig for his bug reports and suggestions

License

This article, along with any associated source code and files, is licensed under The GNU Lesser General Public License (LGPLv3)