Как асинхронно получать данные автозаполнения для TextBox?

Наше приложение WinForms выполняет отложенную загрузку данных для автоматического заполнения текстового поля. Псевдокод для этого выглядит следующим образом:

  1. Типы пользователей в TextBox
  2. При вводе паузы определите, нужно ли нам получать данные автозаполнения
  3. В рабочем потоке свяжитесь с сервером и получите данные
  4. Обратный вызов в поток пользовательского интерфейса
  5. Установить textBox.AutoCompleteCustomSource = fetchedAutoCompleteStringCollection;
  6. Заставьте текстовое поле раскрыться в раскрывающемся списке автозаполнения.

У меня сейчас проблемы с №6. В качестве хака я делаю следующее, чтобы имитировать нажатие клавиши, которое работает, но не во всех ситуациях.

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

Должен быть способ получше. Не могу поверить, что я единственный, кто асинхронно извлекает данные автозаполнения. Как мне это делать?

РЕДАКТИРОВАТЬ: вызов Win32, вызывающий раскрывающееся меню автозавершения, будет приемлемым. Я не возражаю против PInvoking, если придется.


person Rob Prouse    schedule 13.01.2009    source источник


Ответы (2)


Я написал класс асинхронного автозаполнения для TextBox, используя только управляемый код. Надеюсь, поможет.

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

Пример реализации:

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

Как это использовать:

 ...
 TextBox myTextbox = new TextBox();
 MockAutoCompleteSource autoComplete =
      new MockAutoCompleteSource(myTextbox);
 ...
person Daniel Peñalba    schedule 01.12.2011
comment
Я не пробовал ваш код, но он выглядит неплохо, поэтому я приму его как ответ. Спасибо. - person Rob Prouse; 10.08.2012

Обычно вы используете COM-взаимодействие и получаете доступ к _ 1_, _ 2_ или _ 3_ интерфейс. К сожалению, ни у одного из них нет методов принудительного раскрытия автозаполнения.

Возможно, вы захотите использовать Spy ++ и посмотреть сообщения Windows, которые отправляются элементу управления при отображении автозаполнения. Вы можете найти командное сообщение, которое активирует его. Конечно, это деталь реализации, но это может быть единственный выход.

person casperOne    schedule 13.01.2009