Как я могу динамически изменять автоматически заполняемые записи в поле со списком С# или текстовом поле?
У меня есть комбобокс на С#, и я хочу использовать с ним автоматически полные предложения, однако я хочу иметь возможность изменять автоматически заполненные записи по типу пользователя, потому что допустимые допустимые записи слишком многочисленны, чтобы заполнить AutoCompleteStringCollection
при запуске.
В качестве примера предположим, что я разрешаю пользователю вводить имя. У меня есть список возможных первых имен ( "Joe" , "John" ) и список фамилий ( "Bloggs", "Smith" ), но если у меня будет тысяча, то это будет миллион возможных строк - слишком много, чтобы добавить автозаполнение. Поэтому сначала я хочу иметь только первые имена в качестве предложений ( "Joe" , "John" ), а затем, как только пользователь набрал первое имя ( "Joe" ), я хочу удалить существующие автоматически завершенные записи и заменить их с новым набором, состоящим из выбранного первого имени, за которым следуют возможные фамилии ( "Joe Bloggs", "Joe Smith" ). Для этого я попробовал следующий код:
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
.
Ни одна из них не помогла, даже в различных комбинациях. Spence предложил, чтобы я попытался переопределить функцию ComboBox
, которая получает список строк для автоматического завершения. Используя рефлектор, я нашел несколько методов в классе ComboBox
, которые выглядят многообещающими - GetStringsForAutoComplete()
и SetAutoComplete()
, но они оба являются частными, поэтому я не могу получить к ним доступ из производного класса. Я больше не мог этого делать.
Я попробовал заменить ComboBox
на TextBox
, потому что интерфейс автозаполнения один и тот же, и я обнаружил, что поведение немного отличается. С TextBox
он работает лучше, поскольку часть Append автозаполнения работает правильно, но в разделе "Предложение" нет - окно предложений кратковременно вспыхивает, а затем сразу исчезает.
Итак, я подумал: "Хорошо, я буду жить без функциональности" Предложить "и просто использовать" Добавить "), однако, когда я устанавливаю AutoCompleteMode
в Append, я получаю исключение нарушения доступа. То же самое происходит и с предложением - единственный режим, который не генерирует исключений, - это SuggestAppend
, даже несмотря на то, что часть предложения не ведет себя правильно.
Я думал, что при использовании управляемого кода на С# было невозможно получить исключения нарушения прав доступа. Avram предложил использовать "блокировку", чтобы исправить это, но я не знаю, что я должен заблокировать - единственное, что имеет член SyncRoot, - это AutoCompleteStringCollection
и блокировка, которая не препятствует исключениям нарушения доступа. Я также попытался заблокировать ComboBox
или TextBox
, но это тоже не помогло. Как я понимаю, блокировка только предотвращает другие блокировки, поэтому, если базовый код не использует блокировку, то мой использование этого не будет иметь никакого значения.
Итогом всего этого является то, что я не могу использовать TextBox
или ComboBox
с динамическим автоматическим завершением. Кто-нибудь знает, как я могу это достичь?
Обновление:
У меня все еще нет работы, но я узнал еще кое-что. Возможно, некоторые из них будут вдохновлять кого-то другого на то, чтобы придумать решение.
Я попробовал заменить ComboBox
на TextBox
, потому что интерфейс автозаполнения один и тот же, и я обнаружил, что поведение немного отличается. С TextBox
он работает лучше, поскольку часть Append автозаполнения работает правильно, но в разделе "Предложение" нет - окно предложений кратковременно вспыхивает, а затем сразу исчезает.
Итак, я подумал: "Хорошо, я буду жить без функции" Предложить "и просто использовать Append вместо этого", однако, когда я устанавливаю AutoCompleteMode
в Append, я получаю исключение нарушения доступа. То же самое происходит и с предложением - единственный режим, который не генерирует исключений, - это SuggestAppend
, даже несмотря на то, что часть предложения не ведет себя правильно.
Я думал, что при использовании управляемого кода С# было исключено исключение из-за нарушения прав доступа, но в любом случае результат заключается в том, что я не могу использовать TextBox
или ComboBox
с любым типом динамического автоматическое завершение. Кто-нибудь знает, как я могу это достичь?
Обновление 2:
Попробовав различные другие вещи, такие как изменение автозаполнения в рабочем потоке и использование BeginInvoke()
для моделирования поведения типа PostMessage(), я, наконец, сдался и только что реализовал свой собственный автоматический полный раскрывающийся список, используя окно списка. Это гораздо более отзывчивое, чем встроенное, и я потратил меньше времени на это, чем на попытку заставить встроенный работать, поэтому урок для всех, кто хочет этого поведения, - вам, вероятно, лучше реализуя его самостоятельно.
Ответы
Ответ 1
У меня была та же проблема, и я нашел очень простой способ обхода. Как и все остальные, я не мог найти никаких средств для управления поведением компонента, поэтому я должен был принять его.
Естественное поведение: вы не можете динамически заполнять список каждый раз, когда пользователь вводит текст в текстовое поле. Вы должны заполнить его один раз, а затем механизм AutoComplete получает контроль. Вывод: вы должны заполнить AutoCompleteCustomSource с каждой возможной записью в вашей базе данных, чтобы она работала так, как мы хотим.
Конечно, это нежизнеспособно, если у вас есть миллионы записей для заполнения списка. Проблемы с производительностью при передаче данных и сам механизм автозаполнения не позволят вам это сделать.
Я нашел компромиссное решение: динамически заполнять AutoCompleteCustomSource каждый раз, когда длина текста достигает точно N символов (3 в моем случае). Это сработало, потому что сложность была резко сокращена. Количество записей, полученных из базы данных, соответствующих этим 3 начальным символам, было достаточно маленьким, чтобы избежать каких-либо проблем с производительностью.
Основной недостаток: пользователям не будет представлен список автозаполнения, пока они не наберут N-й char. Но похоже, что пользователи не ожидают значимого списка автозаполнения до ввода трех символов.
Надеюсь, что это поможет.
Ответ 2
Я думаю, вы, возможно, захотите выйти из рефлектора и посмотреть, как переопределить поведение автозаполнения в самой панели. Я уверен, что автозаполнение вызовет функцию, которая обращается к списку автозаполнения. Если вы можете найти эту функцию и переопределить ее, вы можете использовать любое поведение, которое вы хотите.
Посмотрите, какую документацию вы можете найти в самом классе combobox.
Ответ 3
Я не тестировал это, но это может стоить того.
Вместо очистки 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;
}
Ответ 4
Это сработало для меня, вы не addRange
к тому же AutoCompleteStringCollection
, а скорее создаете новый каждый раз.
form.fileComboBox.TextChanged += (sender, e) => {
var autoComplete = new AutoCompleteStringCollection();
string[] items = CustomUtil.GetFileNames();
autoComplete.AddRange(items);
form.fileComboBox.AutoCompleteCustomSource = autoComplete;
};
Ответ 5
обновление:
Основная причина, положенная на этот замок, -
его рабочий:)
большинство "таинственных исключений", которые я когда-либо имел, после этого трюка исчезают
- блокировка, как в этом коде, может помочь с вашим исключением
- как вы упомянули ранее, проблема с использованием текстового поля меньше.
- в этом коде, SuggestAppend работает нормально
private void Form1_Load(object sender, EventArgs e)
{
textBox1.AutoCompleteMode = AutoCompleteMode.SuggestAppend;
textBox1.AutoCompleteSource = AutoCompleteSource.CustomSource;
textBox1.TextChanged+=new EventHandler(textBox1_TextChanged);
col1.AddRange(new string[] { "avi avi", "avram avram" });
col2.AddRange(new string[] { "boria boria", "boris boris" });
textBox1.AutoCompleteCustomSource = col1;
textBox1.AutoCompleteMode = AutoCompleteMode.SuggestAppend;
}
AutoCompleteStringCollection col1 = new AutoCompleteStringCollection();
AutoCompleteStringCollection col2 = new AutoCompleteStringCollection();
object locker = new object();
private void textBox1_TextChanged(object sender, EventArgs e)
{
lock (locker)
{
if (textBox1.Text.StartsWith("a") && textBox1.AutoCompleteCustomSource != col1)
{
textBox1.AutoCompleteCustomSource = col1;
}
if (textBox1.Text.StartsWith("b") && textBox1.AutoCompleteCustomSource != col2)
{
textBox1.AutoCompleteCustomSource = col2;
}
}
}
Ответ 6
if(!textBox3.AutoCompleteCustomSource.Contains(textBox3.Text))
textBox3.AutoCompleteCustomSource.Add(textBox3.Text);
Ответ 7
Сэм, ты понял это? Я сталкиваюсь с той же ситуацией. Очевидно, что Clear() вызывает исключение. Я удалил вызов, чтобы очистить, и я получаю правильное предложение, хотя коллекция продолжает расти...
Кроме того, в отношении частных членов: вы можете получить к ним доступ с помощью отражения:
PropertyInfo[] props = [object].GetType().GetProperties({flags go here});
props[0].SetValue(this, new object[] { 0 });
Ответ 8
Я пришел сюда изначально ища решение, но теперь нашел свое.
Хитрость заключается не в вызове Clear() в AutoCompleteCustomSource, а в том, чтобы удалить все элементы в цикле for и затем перестроить список с новыми данными. В моем случае (приложение для коллекции книг) я беру имена авторов из базы данных с конкретным стартовым письмом, а не всей партией. Обратите внимание, что это будет работать только в том случае, если часть текстового поля в поле со списком опущена.
private void cboAuthor_KeyDown(object sender, KeyEventArgs e)
{
if (cboAuthor.Text.Length == 0)
{
// Next two lines simple load data from the database in the
// into a collection (var gateway), base on first letter in
// the combobox. This is specific to my app.
var gateway = new AuthorTableGateway();
gateway.LoadByFirstLetter(Char.ConvertFromUtf32(e.KeyValue)[0]);
// Clear current source without calling Clear()
for (int i = 0; i < authorsAutoComplete.Count; i++)
authorsAutoComplete.RemoveAt(0);
// Rebuild with new data
foreach (var author in gateway)
authorsAutoComplete.Add(author.AuthorName);
}
}
Ответ 9
Не пытайтесь это сделать, но для вашего конкретного случая вы можете закодировать что-то вроде:
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 );
}
}
Ответ 10
Для меня секрет заключался в использовании события TextChanged и ни одного из KeyDown/Up/Press и т.д.
Обновление: После того, как у меня возникли другие проблемы с динамическим изменением AutoCompleteCustomSource, я в конце концов отказался от использования встроенной функции Autocomplete и реализовал свою собственную работу в гораздо более короткие сроки, чем я потратил на нее изначально. Кажется, что в неуправляемом коде есть некоторые проблемы, которые реализуют элемент управления ComboBox. В частности, у меня возникли проблемы с запуском обработчика событий TextChanged, когда это необходимо. Я решил использовать только обработчики OnKeyDown/Press/Up в своей пользовательской реализации, и это казалось более надежным.
Ответ 11
Лучшим решением для этого является использование обработчиков событий combobox. Используя
textUpdate KeyDown DropDown и ChangeCommit, вы можете имитировать autocompletemode, и вы можете настроить, что искать и что отображаться в раскрывающемся списке.
Я нашел этот полезным, но он закодирован в Visual С++, и это toolstripcombobox, но концепция идентична. В любом случае существует огромное сходство С# и С++ в .net, и это не должно быть проблемой при понимании решения.
Индивидуальный автозапуск ToolStripCombobox в Visual С++
Ответ 12
Это очень старая проблема, которую я знаю, но она существует и сегодня.
Мое обходное решение состояло в том, чтобы установить для режима Autocomplete и исходных свойств значение "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;
}
Ответ 13
используйте этот код
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;
}
}