Как я могу динамически изменять записи автозаполнения в поле со списком C # или текстовое поле? - PullRequest
46 голосов
/ 05 февраля 2009

У меня есть поле со списком в C #, и я хочу использовать предложения автозаполнения с ним, однако я хочу иметь возможность изменять записи автозаполнения по мере ввода пользователем, потому что возможных допустимых записей слишком много, чтобы заполнить AutoCompleteStringCollection при запуске.

В качестве примера предположим, что я разрешаю пользователю вводить имя. У меня есть список возможных имен («Джо», «Джон») и список фамилий («Блоггс», «Смит»), но если у меня будет тысяча каждого из них, то это будет миллион возможных строк - слишком много, чтобы положить в автозаполнение записей. Итак, сначала я хочу иметь только первые имена в качестве предложений («Джо», «Джон»), а затем, как только пользователь набрал первое имя («Джо»), я хочу удалить существующие записи автозаполнения и заменить они с новым набором, состоящим из выбранного имени, за которым следуют возможные фамилии («Джо Блоггс», «Джо Смит»). Для этого я попробовал следующий код:

void InitializeComboBox()
{
    ComboName.AutoCompleteMode = AutoCompleteMode.SuggestAppend;
    ComboName.AutoCompleteSource = AutoCompleteSource.CustomSource;
    ComboName.AutoCompleteCustomSource = new AutoCompleteStringCollection();
    ComboName.TextChanged += new EventHandler( ComboName_TextChanged );
}

void ComboName_TextChanged( object sender, EventArgs e )
{
    string text = this.ComboName.Text;
    string[] suggestions = GetNameSuggestions( text );

    this.ComboQuery.AutoCompleteCustomSource.Clear();
    this.ComboQuery.AutoCompleteCustomSource.AddRange( suggestions );
}

Однако это не работает должным образом. Кажется, что вызов Clear () заставляет механизм автозаполнения «выключаться» до тех пор, пока следующий символ не появится в поле со списком, но, конечно, когда следующий символ появляется, вышеуказанный код снова вызывает Clear (), поэтому пользователь никогда не будет на самом деле видит автоматическую полную функциональность. Это также приводит к тому, что все содержимое поля со списком становится выбранным, поэтому между каждым нажатием клавиши вы должны отменить выделение существующего текста, что делает его непригодным для использования. Если я удаляю вызов Clear (), то автозаполнение работает, но кажется, что тогда вызов AddRange() не имеет никакого эффекта, потому что новые предложения, которые я добавляю, не отображаются в выпадающем списке автозаполнения.

Я искал решение для этого и видел различные предложенные варианты, но я не могу заставить их работать - либо функция автозаполнения отключена, либо новые строки не отображаются. Вот список вещей, которые я пробовал:

  • Вызов BeginUpdate() перед сменой строк и EndUpdate() после.
  • Вызов Remove() для всех существующих строк вместо Clear ().
  • Очистка текста из выпадающего списка во время обновления строк и последующее добавление его обратно.
  • Установка AutoCompleteMode на «None», пока я меняю строки, а затем возвращение к «SuggestAppend».
  • Перехват события TextUpdate или KeyPress вместо TextChanged.
  • Замена существующего AutoCompleteCustomSource новым AutoCompleteStringCollection каждый раз.

Ничто из этого не помогло, даже в различных комбинациях. Спенс предложил мне попробовать переопределить функцию ComboBox, которая получает список строк для использования в автозаполнении. Используя отражатель, я нашел несколько методов в классе ComboBox, которые выглядят многообещающе - GetStringsForAutoComplete() и SetAutoComplete(), но оба они закрытые, поэтому я не могу получить к ним доступ из производного класса. Я не мог продолжать это.

Я попытался заменить ComboBox на TextBox, потому что интерфейс автозаполнения такой же, и я обнаружил, что поведение немного отличается. С TextBox, кажется, он работает лучше, так как часть добавления автозаполнения работает правильно, а часть «Предлагать» - не работает - окно предложений кратковременно мигает, но затем сразу исчезает.

Поэтому я подумал: «Хорошо, я буду жить без функции« Предложить »и просто буду использовать« Добавить »» », однако, когда я установил AutoCompleteMode на« Добавить », я получил исключение нарушения прав доступа. То же самое происходит с Suggest - единственный режим, который не генерирует исключения, это SuggestAppend, хотя часть Suggest тогда не будет работать правильно.

Я думал, что при использовании управляемого кода на C # было невозможно получить исключения нарушения доступа. Avram предложил использовать «lock», чтобы исправить это, но я не знаю, что мне нужно блокировать - единственное, что имеет член SyncRoot, это AutoCompleteStringCollection, и блокировка, которая не мешает исключения нарушения доступа. Я также попытался заблокировать ComboBox или TextBox, но это тоже не помогло. Насколько я понимаю, блокировка предотвращает только другие блокировки, поэтому, если базовый код не использует блокировку, то использование ее не будет иметь никакого значения.

Результатом всего этого является то, что в настоящее время я не могу использовать TextBox или ComboBox с динамическим автоматическим завершением. У кого-нибудь есть идеи, как мне этого добиться?

Обновление:

У меня все еще нет этой работы, но я узнал еще кое-что. Возможно, что-то из этого вдохновит кого-то другого на решение проблемы.

Я попытался заменить ComboBox на TextBox, потому что интерфейс автозаполнения такой же, и я обнаружил, что поведение немного отличается. С TextBox он работает лучше, так как часть «Автоматическое завершение» «Добавить» работает правильно, а «Предлагать» - нет, поле с подсказкой кратковременно мигает, но затем сразу исчезает.

Поэтому я подумал: «Хорошо, я буду жить без функции« Предложить »и просто вместо этого буду использовать« Добавить »» », однако, когда я установил для« * 1072 »значение« Добавить », я получил исключение нарушения прав доступа. То же самое происходит с Suggest - единственный режим, который не генерирует исключения, это SuggestAppend, хотя часть Suggest тогда не будет работать правильно.

Я думал, что при использовании управляемого кода C # было невозможно получить исключения нарушения доступа, но в любом случае, в результате я не могу использовать TextBox или ComboBox с каким-либо динамическим Авто завершено. Кто-нибудь знает, как мне этого добиться?

Обновление 2:

После попытки различных других вещей, таких как изменение автозаполнения в рабочем потоке и использование BeginInvoke() для имитации поведения типа PostMessage (), я, наконец, сдался и просто реализовал свой собственный выпадающий список автозаполнения, используя список. Он гораздо более отзывчивый, чем встроенный, и я потратил на это меньше времени, чем пытался заставить встроенный работать, поэтому урок для всех, кто хочет такого поведения, - вы, вероятно, лучше реализовать это самостоятельно.

Ответы [ 14 ]

13 голосов
/ 06 декабря 2011

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

Естественное поведение таково: вы не можете динамически заполнять список каждый раз, когда пользователь вводит текстовое поле. Вы должны заполнить его один раз, и тогда механизм AutoComplete берет на себя управление. Вывод таков: вы должны заполнить AutoCompleteCustomSource каждой возможной записью в вашей базе данных, чтобы она работала так, как мы хотим.

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

Компромиссное решение, которое я нашел, заключалось в следующем: динамически заполнять AutoCompleteCustomSource каждый раз, когда длина текста достигает ровно N символов (3 в моем случае). Это работало, потому что сложность была резко уменьшена. Количество записей, извлекаемых из базы данных, которые соответствуют этим 3 начальным символам, было достаточно маленьким, чтобы избежать проблем с производительностью.

Основной недостаток: пользователям не будет представлен список автозаполнения, пока они не наберу N-й символ. Но кажется, что пользователи не ожидают значимого списка автозаполнения, прежде чем набрать 3 символа.

Надеюсь, это поможет.

2 голосов
/ 29 июня 2013

Это сработало для меня, вы не addRange к тому же AutoCompleteStringCollection, а скорее создаете новый каждый раз.

form.fileComboBox.TextChanged += (sender, e) => {
    var autoComplete = new AutoCompleteStringCollection();
    string[] items = CustomUtil.GetFileNames();
    autoComplete.AddRange(items);
    form.fileComboBox.AutoCompleteCustomSource = autoComplete;
};
1 голос
/ 05 февраля 2009

Я не проверял это, но, возможно, стоит попробовать.

Вместо очистки AutoCompleteCustomSource создайте двойной буфер, сохранив два экземпляра. Когда текст изменится, вызовите GetNameSuggestions () и создайте строки для той, которая в данный момент не используется, а затем установите ComboName.AutoCompleteCustomSource на тот, который вы только что установили.

Я думаю, это должно выглядеть примерно так.

AutoCompleteCustomSource accs_a;
AutoCompleteCustomSource accs_b;
bool accs_check = true; //true for accs_a, false for accs_b
void InitializeComboBox()
{
    ComboName.AutoCompleteMode = AutoCompleteMode.SuggestAppend;
    ComboName.AutoCompleteSource = AutoCompleteSource.CustomSource;

    accs_a = new AutoCompleteStringCollection();
    accs_b = new AutoCompleteStringCollection();

    ComboName.AutoCompleteCustomSource = accs_a;
    ComboName.TextChanged += new EventHandler( ComboName_TextChanged );
}

void ComboName_TextChanged( object sender, EventArgs e )
{
    string text = this.ComboName.Text;

    if(accs_check)
    {
       accs_b.Clear();
       accs_b.AddRange(GetNameSuggestions( text ));
       accs_check = false;
    }
    else
    {
       accs_a.Clear();
       accs_a.AddRange(GetNameSuggestions( text ));
       accs_check = true;
    }

    this.ComboQuery.AutoCompleteCustomSource = accs_check? accs_a : accs_b;
}
1 голос
/ 05 февраля 2009

Я думаю, вы, возможно, захотите достать отражатель и посмотреть на переопределение поведения автозаполнения в самом комбобоксе. Я уверен, что автозаполнение вызовет функцию, которая обращается к списку автозаполнения. Если вы можете найти эту функцию и переопределить ее, вы можете использовать любое поведение, какое захотите.

Посмотрите, какую документацию вы можете найти в самом классе combobox.

0 голосов
/ 29 ноября 2018

Попробовав все решения, которые были предложены здесь (безуспешно), я нашел что-то, что работает для меня:

private void CellBox_TextChanged(object sender, EventArgs e)
{
    ((TextBox)sender).TextChanged -= CellBox_TextChanged;
    ((TextBox)dataGridView1.EditingControl).AutoCompleteMode = AutoCompleteMode.None;
    ((TextBox)dataGridView1.EditingControl).AutoCompleteCustomSource = null;                
    aCSC.Clear();
    foreach (string value in Autocompletevalues())
    {
        aCSC.Add(value);
    }
    ((TextBox)dataGridView1.EditingControl).AutoCompleteCustomSource = aCSC;
    ((TextBox)dataGridView1.EditingControl).AutoCompleteMode = AutoCompleteMode.Suggest;
    ((TextBox)sender).TextChanged += CellBox_TextChanged;
}

Шаги:

  • Отключить обработчик событий
  • Отключить режим автозаполнения
  • Установить для источника значение null
  • Обновление AutoCompleteStringCollection (aCSC)
  • Установить для источника обновленную AutoCompleteStringCollection
  • Активировать режим автозаполнения
  • включить Eventhandler

Надеюсь, это кому-нибудь поможет ..

0 голосов
/ 23 апреля 2018

Это очень старая проблема, которую я знаю, но она существует и сегодня. Мой обходной путь состоял в том, чтобы установить режим автозаполнения и свойства источника равными «none» и вручную обновить элементы в событии KeyUp.

Я уверен, что это безрассудно, но он прекрасно работает без проблем в течение долгого времени, независимо от скорости ввода данных, с дополнительным бонусом моих волос, начинающим расти.

Вы также можете выбрать, предлагать или просто предлагать и добавлять. Я надеюсь, что это может кому-то помочь.

private void comboBox1_KeyUp(object sender, KeyEventArgs e)
    {

        if (string.IsNullOrWhiteSpace(comboBox1.Text))
        {
            e.Handled = true;
            return;
        }
        if (comboBox1.Text.Length < 3)
        {
            e.Handled = true;
            return;
        }

        if (e.KeyCode == Keys.Down || e.KeyCode == Keys.Up)
        {
            e.Handled = true;
            return;
        }
        else if (e.KeyCode == Keys.Back)
        {
            e.Handled = true;
            return;
        }

        string text = comboBox1.Text;

        if (e.KeyCode == Keys.Enter)
        {
            comboBox1.DroppedDown = false;
            comboBox1.SelectionStart = text.Length;
            e.Handled = true;
            return;
        }

        List<string> LS = Suggestions(comboBox1.Text);

        comboBox1.Items.Clear();
        comboBox1.Items.AddRange(LS.ToArray());

        //If you do not want to Suggest and Append
        //comment the following line to only Suggest
        comboBox1.Focus();

        comboBox1.DroppedDown = true;
        comboBox1.SelectionStart = text.Length;

        //Prevent cursor from getting hidden
        Cursor.Current = Cursors.Default;
        e.Handled = true;
    }
0 голосов
/ 07 января 2015

Лучшее решение для этого - использовать обработчики событий combobox. Используя textUpdate KeyDown DropDown и ChangeCommit , вы можете имитировать автозаполнение, а также настраивать, что искать и что отображать в раскрывающемся списке.

Я нашел этот ответ полезным, но он закодирован в Visual C ++ и является toolstripcombobox, но концепция идентична . В любом случае, в .net существует огромное сходство c # и c ++, и это не должно быть проблемой для понимания решения.

Настраиваемый автоматический поиск ToolStripCombobox в Visual C ++

0 голосов
/ 13 декабря 2013

используйте этот код

private void dataGridView1_EditingControlShowing(object sender,DataGridViewEditingControlShowingEventArgs e)
    {

        if (e.Control is DataGridViewComboBoxEditingControl)
        {
            ((ComboBox)e.Control).DropDownStyle = ComboBoxStyle.DropDown;
            ((ComboBox)e.Control).AutoCompleteSource = AutoCompleteSource.ListItems;
            ((ComboBox)e.Control).AutoCompleteMode = System.Windows.Forms.AutoCompleteMode.Suggest;
        }

}
0 голосов
/ 22 сентября 2011

Для меня секретом было использование события TextChanged, а не KeyDown / Up / Press и т. Д.

Обновление: После других проблем с динамическим изменением AutoCompleteCustomSource я, в конечном счете, отказался от использования встроенной функции Autocomplete и реализовал свою собственную за гораздо более короткое время, чем изначально потратил на нее. Кажется, есть некоторые проблемы в неуправляемом коде, который реализует элемент управления ComboBox. В частности, у меня были проблемы с обработчиком события TextChanged, когда он должен был. Я решил использовать обработчики OnKeyDown / Press / Up только в своей пользовательской реализации, и это казалось более надежным.

0 голосов
/ 27 февраля 2011

Не пробуйте это, но для вашего конкретного случая вы можете написать что-то вроде:

    private void txtAutoComplete_KeyUp(object sender, KeyEventArgs e)
    {

        String text = txtAutoComplete.Text;

        if (text.EndsWith(" "))
        {

            string[] suggestions = GetNameSuggestions( text ); //put [text + " "] at the begin of each array element
            txtAutoComplete.AutoCompleteCustomSource.Clear();
            txtAutoComplete.AutoCompleteCustomSource.AddRange( suggestions );

        }

    }
...