Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to Asynchronously fetch AutoComplete data for a TextBox?

Our WinForms application does lazy loading of the data for auto complete of a textbox. The pseudocode for this is as follows;

  1. User types in TextBox
  2. On typing pause, determine if we need to fetch the auto complete data
  3. In worker thread, contact the server and fetch data
  4. Invoke back to the UI thread
  5. Set textBox.AutoCompleteCustomSource = fetchedAutoCompleteStringCollection;
  6. Force the textbox to drop down it's autocomplete dropdown.

I am currently having trouble with #6. As a hack, I do the following to simulate a keypress which works, but it does not work in all situations.

     // This is a hack, but the only way that I have found to get the autocomplete
     // to drop down once the data is returned.
     textBox.SelectionStart = textBox.Text.Length;
     textBox.SelectionLength = 0;
     SendKeys.Send( " {BACKSPACE}" );

There must be a better way. I can't believe that I am the only person fetching auto complete data asynchronously. How should I be doing this?

EDIT: A Win32 call to cause the Auto Complete to dropdown would be acceptable. I don't mind PInvoking out if I have to.

like image 712
Rob Prouse Avatar asked Jan 13 '09 18:01

Rob Prouse


2 Answers

I have written an async autocomplete class for a TextBox using only managed code. Hope it helps.

using System;
using System.Windows.Forms;
using System.Collections.Generic;
using System.Text;
using System.ComponentModel;

namespace TextboxAutocomplete
{
    public abstract class AutoCompleteSource
    {
        private TextBox mTextBox;
        private AutoCompleteMode mAutoCompleteMode;

        public AutoCompleteSource(TextBox textbox) :
            this(textbox, AutoCompleteMode.Suggest) { }

        public AutoCompleteSource(TextBox textbox, AutoCompleteMode mode)
        {
            if (textbox == null)
                throw new ArgumentNullException("textbox");

            if (textbox.IsDisposed)
                throw new ArgumentException("textbox");

            mTextBox = textbox;
            mAutoCompleteMode = mode;

            mTextBox.AutoCompleteSource = System.Windows.Forms.AutoCompleteSource.None;

            BackgroundWorker autoCompleteLoader = new BackgroundWorker();
            autoCompleteLoader.DoWork += new DoWorkEventHandler(autoCompleteLoader_DoWork);
            autoCompleteLoader.RunWorkerCompleted += new RunWorkerCompletedEventHandler(autoCompleteLoader_RunWorkerCompleted);
            autoCompleteLoader.RunWorkerAsync();
        }

        void autoCompleteLoader_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            AutoCompleteStringCollection collection = e.Result as AutoCompleteStringCollection;
            if (collection == null) return;

            if (mTextBox.InvokeRequired)
            {
                mTextBox.Invoke(new SetAutocompleteSource(DoSetAutoCompleteSource), new object[] { collection });
            }
            else
            {
                DoSetAutoCompleteSource(collection);
            }
        }

        protected void DoSetAutoCompleteSource(AutoCompleteStringCollection collection)
        {
            if (mTextBox.IsDisposed) return;

            mTextBox.AutoCompleteMode = mAutoCompleteMode;
            mTextBox.AutoCompleteSource = System.Windows.Forms.AutoCompleteSource.CustomSource;
            mTextBox.AutoCompleteCustomSource = collection;
        }

        void autoCompleteLoader_DoWork(object sender, DoWorkEventArgs e)
        {
            List<string> autoCompleteItems = GetAutocompleteItems();
            if (autoCompleteItems == null) return;
            AutoCompleteStringCollection collection = new AutoCompleteStringCollection();
            collection.AddRange(GetAutocompleteItems().ToArray());
            e.Result = collection;
        }

        protected abstract List<string> GetAutocompleteItems();
    }

    internal delegate void SetAutocompleteSource(AutoCompleteStringCollection collection);
}

Sample implementation:

using System;
using System.Windows.Forms;
using System.Collections.Generic;
using System.Text;

namespace TextboxAutocomplete
{
    class MockAutoCompleteSource : AutoCompleteSource
    {
        public MockAutoCompleteSource(TextBox textbox)
            : base(textbox)
        {

        }

        protected override List<string> GetAutocompleteItems()
        {
            List<string> result = new List<string>();
            for (int i = 0; i < 2500; i++)
            {
                result.Add(Guid.NewGuid().ToString());
            }

            return result;
        }
    }
}

How to use it:

 ...
 TextBox myTextbox = new TextBox();
 MockAutoCompleteSource autoComplete =
      new MockAutoCompleteSource(myTextbox);
 ...
like image 184
Daniel Peñalba Avatar answered Nov 05 '22 16:11

Daniel Peñalba


Typically, you would use COM interop and access the IAutoComplete, IAutoComplete2, or the IAutoCompleteDropDown interface. Unfortunately, none of these has methods to force the autocomplete to drop down.

You might want to use Spy++ and look at the windows messages that are being sent to the control when the auto complete displays. You might find a command message which will activate it. Of course, this is an implementation detail, but it might be the only way to go here.

like image 29
casperOne Avatar answered Nov 05 '22 15:11

casperOne