0

I am trying to create a text input field with autocomplete functionality. The list of available options is huge (50,000+) and will need to be queried on TextChanged (after the first 3 characters have been entered).

I have a 99%-working solution with TextBox, setting AutoCompleteCustomSource to my new AutoCompleteStringCollection in the TextChanged event, but that results in occasional memory access violations due to a well-documented bug in the underlying AutoComplete implementation...

Microsoft Support say "Do not modify the AutoComplete candidate list dynamically during key events"...

Several SO threads: 1, 2, 3

These threads have some suggestions on how to prevent the exceptions but nothing seems to completely eliminate them, so I'm looking for an alternative. have tried switching to a ComboBox-based solution but can't get it to behave as I want.

  • After the user types the third character, I update the ComboBox's DataSource but the first item is automatically selected. The user is not able to continue typing the rest of the name.

  • The ComboBox items are not visible until the user clicks the triangle to expand the list

  • If the user selects the text they have entered and starts typing, I set DataSource to null to remove the list of suggestions. Doing this puts the cursor at the start of the text, so their characters appear in completely the wrong order!

My View:

    public event EventHandler SearchTextChanged;
    public event EventHandler InstrumentSelected;

    public Instrument CurrentInstrument
    {
        get { return comboBoxQuickSearch.SelectedItem as Instrument; }
    }

    public IEnumerable<Instrument> Suggestions
    {
        get { return comboBoxQuickSearch.DataSource as IEnumerable<Instrument>; }
        set
        {
            comboBoxQuickSearch.DataSource = value;
            comboBoxQuickSearch.DisplayMember = "Name";
        }
    }

    public string SearchText
    {
        get { return comboBoxQuickSearch.Text; }
    }

    private void comboBoxQuickSearch_TextChanged(object sender, EventArgs e)
    {
        if (SearchTextChanged != null)
        {
            SearchTextChanged(sender, e);
        }
    }

    private void comboBoxQuickSearch_KeyDown(object sender, KeyEventArgs e)
    {
        if (e.KeyCode == Keys.Enter && InstrumentSelected != null)
        {
            InstrumentSelected(sender, e);
        }
    }

My Presenter:

    private void SearchTextChanged(object sender, EventArgs e)
    {
        lock (searchLock)
        {
            // Do not update list of suggestions if:
            // 1) an instrument has already been selected
            //    (the user may be scrolling through suggestion list)
            // 2) a search has taken place within the last MIN_SEARCH_INTERVAL
            if (DateTime.Now - quickSearchTimeStamp < minimumSearchInterval
                || (view.Suggestions != null && view.Suggestions.Any(i => i.Name == view.SearchText)))
            {
                return;
            }

            string searchText = view.SearchText.Trim();
            if (searchText.Length < SEARCH_PREFIX_LENGTH)
            {
                // Do not show suggestions
                view.Suggestions = null;
                searchAgain = false;
            }
            // If the prefix has been entered or changed,
            // or another search is needed to display the full sublist
            else if (searchText.Length == SEARCH_PREFIX_LENGTH
                  || searchText.Substring(0, SEARCH_PREFIX_LENGTH) != searchTextPrefix
                  || searchAgain)
            {
                // Record the current time and prefix
                quickSearchTimeStamp = DateTime.Now;
                searchTextPrefix = searchText.Substring(0, SEARCH_PREFIX_LENGTH);

                // Query matches from DB
                IList<Instrument> matches = QueryMatches(searchText);

                // Update suggestions
                view.Suggestions = matches;

                // If a large number of results was received, search again on the next chararacter
                // This ensures the full match list is presented
                searchAgain = matches.Count() > MAX_RESULTS;
            }
        }
    }

(The searchAgain bit is left over from the TextBox implementation, where the AutoCompleteCustomSource wouldn't always show the complete list if it contained too many items.)

Can I get the ComboBox to work as I want it to, providing suggestions as the user types, given my requirement to query those suggestions on TextChanged?

Is there some other combination of controls I should use for a better user experience, e.g. ListBox?

Community
  • 1
  • 1
Chris B
  • 709
  • 2
  • 14
  • 32
  • Try to update after textchanged event is handled. (i.e. add time delay) – Christian Irwan Hadi Wicaksana May 09 '15 at 16:51
  • Thanks for your reply. What do you think is the most reliable way to do that? Interestingly, updating the AutoCompleteDataSource asynchronously after TextChanged does not result in the suggestions list 'dropping down' below the textbox. – Chris B May 11 '15 at 17:44

0 Answers0