С# winforms combobox динамический автозаполнение
Моя проблема аналогична этой: Как я могу динамически изменять автоматически заполняемые записи в Cobbox или текстовое поле?
Но я все еще не нахожу решения.
Проблема кратко:
У меня есть ComboBox
и большое количество записей. Когда пользователь начинает печатать, я хочу загрузить записи, которые начинаются с текста ввода, и предлагать пользователю автозаполнение.
Как описано выше, я не могу загрузить их на сomboBox_TextChanged
, потому что я всегда перезаписываю предыдущие результаты и не вижу их.
Могу ли я реализовать это, используя только ComboBox
? (не TextBox
или ListBox
)
Я использую следующие настройки:
сomboBox.AutoCompleteMode = AutoCompleteMode.SuggestAppend;
сomboBox.AutoCompleteSource = AutoCompleteSource.CustomSource;
Ответы
Ответ 1
Вот мое окончательное решение. Он отлично работает с большим количеством данных. Я использую Timer
, чтобы убедиться, что пользователь хочет найти текущее значение. Он выглядит сложным, но это не так.
Спасибо Макс Ламбертни за идею.
private bool _canUpdate = true;
private bool _needUpdate = false;
//If text has been changed then start timer
//If the user doesn't change text while the timer runs then start search
private void combobox1_TextChanged(object sender, EventArgs e)
{
if (_needUpdate)
{
if (_canUpdate)
{
_canUpdate = false;
UpdateData();
}
else
{
RestartTimer();
}
}
}
private void UpdateData()
{
if (combobox1.Text.Length > 1)
{
List<string> searchData = Search.GetData(combobox1.Text);
HandleTextChanged(searchData);
}
}
//If an item was selected don't start new search
private void combobox1_SelectedIndexChanged(object sender, EventArgs e)
{
_needUpdate = false;
}
//Update data only when the user (not program) change something
private void combobox1_TextUpdate(object sender, EventArgs e)
{
_needUpdate = true;
}
//While timer is running don't start search
//timer1.Interval = 1500;
private void RestartTimer()
{
timer1.Stop();
_canUpdate = false;
timer1.Start();
}
//Update data when timer stops
private void timer1_Tick(object sender, EventArgs e)
{
_canUpdate = true;
timer1.Stop();
UpdateData();
}
//Update combobox with new data
private void HandleTextChanged(List<string> dataSource)
{
var text = combobox1.Text;
if (dataSource.Count() > 0)
{
combobox1.DataSource = dataSource;
var sText = combobox1.Items[0].ToString();
combobox1.SelectionStart = text.Length;
combobox1.SelectionLength = sText.Length - text.Length;
combobox1.DroppedDown = true;
return;
}
else
{
combobox1.DroppedDown = false;
combobox1.SelectionStart = text.Length;
}
}
Это решение не очень круто. Поэтому, если у кого-то есть другое решение, поделитесь им со мной.
Ответ 2
Я также сталкиваюсь с такими требованиями в последнее время. Я устанавливаю следующие свойства без написания кода, который он работает. Если это вам поможет.
![введите описание изображения здесь]()
Ответ 3
Да, вы наверняка можете... но ему нужна работа, чтобы он работал без проблем. Это какой-то код, который я придумал. Имейте в виду, что не использует функции автообновления combobox, и это может быть довольно медленно, если вы используете его для просеивания большого количества предметов...
string[] data = new string[] {
"Absecon","Abstracta","Abundantia","Academia","Acadiau","Acamas",
"Ackerman","Ackley","Ackworth","Acomita","Aconcagua","Acton","Acushnet",
"Acworth","Ada","Ada","Adair","Adairs","Adair","Adak","Adalberta","Adamkrafft",
"Adams"
};
public Form1()
{
InitializeComponent();
}
private void comboBox1_TextChanged(object sender, EventArgs e)
{
HandleTextChanged();
}
private void HandleTextChanged()
{
var txt = comboBox1.Text;
var list = from d in data
where d.ToUpper().StartsWith(comboBox1.Text.ToUpper())
select d;
if (list.Count() > 0)
{
comboBox1.DataSource = list.ToList();
//comboBox1.SelectedIndex = 0;
var sText = comboBox1.Items[0].ToString();
comboBox1.SelectionStart = txt.Length;
comboBox1.SelectionLength = sText.Length - txt.Length;
comboBox1.DroppedDown = true;
return;
}
else
{
comboBox1.DroppedDown = false;
comboBox1.SelectionStart = txt.Length;
}
}
private void comboBox1_KeyUp(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.Back)
{
int sStart = comboBox1.SelectionStart;
if (sStart > 0)
{
sStart--;
if (sStart == 0)
{
comboBox1.Text = "";
}
else
{
comboBox1.Text = comboBox1.Text.Substring(0, sStart);
}
}
e.Handled = true;
}
}
Ответ 4
Я написал что-то вроде этого....
private void frmMain_Load(object sender, EventArgs e)
{
cboFromCurrency.Items.Clear();
cboComboBox1.AutoCompleteMode = AutoCompleteMode.Suggest;
cboComboBox1.AutoCompleteSource = AutoCompleteSource.ListItems;
// Load data in comboBox => cboComboBox1.DataSource = .....
// Other things
}
private void cboComboBox1_KeyPress(object sender, KeyPressEventArgs e)
{
cboComboBox1.DroppedDown = false;
}
Чтобы все (Y)
Ответ 5
Я нашел Max Lambertini ответ очень полезным, но изменил его метод HandleTextChanged как таковой:
//I like min length set to 3, to not give too many options
//after the first character or two the user types
public Int32 AutoCompleteMinLength {get; set;}
private void HandleTextChanged() {
var txt = comboBox.Text;
if (txt.Length < AutoCompleteMinLength)
return;
//The GetMatches method can be whatever you need to filter
//table rows or some other data source based on the typed text.
var matches = GetMatches(comboBox.Text.ToUpper());
if (matches.Count() > 0) {
//The inside of this if block has been changed to allow
//users to continue typing after the auto-complete results
//are found.
comboBox.Items.Clear();
comboBox.Items.AddRange(matches);
comboBox.DroppedDown = true;
Cursor.Current = Cursors.Default;
comboBox.Select(txt.Length, 0);
return;
}
else {
comboBox.DroppedDown = false;
comboBox.SelectionStart = txt.Length;
}
}
Ответ 6
Этот код записывается в вашу форму. Он отображает всю базу данных Tour в базе данных, когда пользователь вводит букву в поле со списком. Этот код автоматически предлагает и добавляет правильный выбор по желанию пользователя.
con.Open();
cmd = new SqlCommand("SELECT DISTINCT Tour FROM DetailsTB", con);
SqlDataReader sdr = cmd.ExecuteReader();
DataTable dt = new DataTable();
dt.Load(sdr);
combo_search2.DisplayMember = "Tour";
combo_search2.DroppedDown = true;
List<string> list = new List<string>();
foreach (DataRow row in dt.Rows)
{
list.Add(row.Field<string>("Tour"));
}
this.combo_search2.Items.AddRange(list.ToArray<string>());
combo_search2.AutoCompleteMode = AutoCompleteMode.SuggestAppend;
combo_search2.AutoCompleteSource = AutoCompleteSource.ListItems;
con.Close();
Ответ 7
В предыдущих ответах есть недостатки. Предлагает собственную версию с выделенным в выпадающем списке нужным элементом:
private ConnectSqlForm()
{
InitializeComponent();
cmbDatabases.TextChanged += UpdateAutoCompleteComboBox;
cmbDatabases.KeyDown += AutoCompleteComboBoxKeyPress;
}
private void UpdateAutoCompleteComboBox(object sender, EventArgs e)
{
var comboBox = sender as ComboBox;
if(comboBox == null)
return;
string txt = comboBox.Text;
string foundItem = String.Empty;
foreach(string item in comboBox.Items)
if (!String.IsNullOrEmpty(txt) && item.ToLower().StartsWith(txt.ToLower()))
{
foundItem = item;
break;
}
if (!String.IsNullOrEmpty(foundItem))
{
if (String.IsNullOrEmpty(txt) || !txt.Equals(foundItem))
{
comboBox.TextChanged -= UpdateAutoCompleteComboBox;
comboBox.Text = foundItem;
comboBox.DroppedDown = true;
Cursor.Current = Cursors.Default;
comboBox.TextChanged += UpdateAutoCompleteComboBox;
}
comboBox.SelectionStart = txt.Length;
comboBox.SelectionLength = foundItem.Length - txt.Length;
}
else
comboBox.DroppedDown = false;
}
private void AutoCompleteComboBoxKeyPress(object sender, KeyEventArgs e)
{
var comboBox = sender as ComboBox;
if (comboBox != null && comboBox.DroppedDown)
{
switch (e.KeyCode)
{
case Keys.Back:
int sStart = comboBox.SelectionStart;
if (sStart > 0)
{
sStart--;
comboBox.Text = sStart == 0 ? "" : comboBox.Text.Substring(0, sStart);
}
e.SuppressKeyPress = true;
break;
}
}
}
Ответ 8
using (var client = new UserServicesClient())
{
var list = new AutoCompleteStringCollection();
list.AddRange(client.ListNames(query).ToArray());
comboBoxName.AutoCompleteCustomSource = list;
}
Ответ 9
Это была большая боль, чтобы работать. Я ударил кучу тупиков, но окончательный результат достаточно прост. Надеюсь, это может принести пользу кому-то. Это может потребоваться немного плюнуть и польский, что и все.
Примечание: _addressFinder.CompleteAsync возвращает список KeyValuePairs.
public partial class MyForm : Form
{
private readonly AddressFinder _addressFinder;
private readonly AddressSuggestionsUpdatedEventHandler _addressSuggestionsUpdated;
private delegate void AddressSuggestionsUpdatedEventHandler(object sender, AddressSuggestionsUpdatedEventArgs e);
public MyForm()
{
InitializeComponent();
_addressFinder = new AddressFinder(new AddressFinderConfigurationProvider());
_addressSuggestionsUpdated += AddressSuggestions_Updated;
MyComboBox.DropDownStyle = ComboBoxStyle.DropDown;
MyComboBox.DisplayMember = "Value";
MyComboBox.ValueMember = "Key";
}
private void MyComboBox_KeyPress(object sender, KeyPressEventArgs e)
{
if (char.IsControl(e.KeyChar))
{
return;
}
var searchString = ThreadingHelpers.GetText(MyComboBox);
if (searchString.Length > 1)
{
Task.Run(() => GetAddressSuggestions(searchString));
}
}
private async Task GetAddressSuggestions(string searchString)
{
var addressSuggestions = await _addressFinder.CompleteAsync(searchString).ConfigureAwait(false);
if (_addressSuggestionsUpdated.IsNotNull())
{
_addressSuggestionsUpdated.Invoke(this, new AddressSuggestionsUpdatedEventArgs(addressSuggestions));
}
}
private void AddressSuggestions_Updated(object sender, AddressSuggestionsUpdatedEventArgs eventArgs)
{
try
{
ThreadingHelpers.BeginUpdate(MyComboBox);
var text = ThreadingHelpers.GetText(MyComboBox);
ThreadingHelpers.ClearItems(MyComboBox);
foreach (var addressSuggestions in eventArgs.AddressSuggestions)
{
ThreadingHelpers.AddItem(MyComboBox, addressSuggestions);
}
ThreadingHelpers.SetDroppedDown(MyComboBox, true);
ThreadingHelpers.ClearSelection(MyComboBox);
ThreadingHelpers.SetText(MyComboBox, text);
ThreadingHelpers.SetSelectionStart(MyComboBox, text.Length);
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
finally
{
ThreadingHelpers.EndUpdate(MyComboBox);
}
}
private class AddressSuggestionsUpdatedEventArgs : EventArgs
{
public IList<KeyValuePair<string, string>> AddressSuggestions { get; private set; }
public AddressSuggestionsUpdatedEventArgs(IList<KeyValuePair<string, string>> addressSuggestions)
{
AddressSuggestions = addressSuggestions;
}
}
}
ThreadingHelpers - это всего лишь набор статических методов формы:
public static string GetText(ComboBox comboBox)
{
if (comboBox.InvokeRequired)
{
return (string)comboBox.Invoke(new Func<string>(() => GetText(comboBox)));
}
lock (comboBox)
{
return comboBox.Text;
}
}
public static void SetText(ComboBox comboBox, string text)
{
if (comboBox.InvokeRequired)
{
comboBox.Invoke(new Action(() => SetText(comboBox, text)));
return;
}
lock (comboBox)
{
comboBox.Text = text;
}
}
Ответ 10
Возьмите 2. Мой ответ ниже не довел меня до желаемого результата, но он может быть полезен кому-то. Функция автоматического выбора ComboBox вызывала у меня большую боль. Этот использует TextBox, сидящий поверх ComboBox, позволяя мне игнорировать все, что появляется в ComboBox, и просто реагировать на событие с измененным выбором.
- Создать форму
- Добавить ComboBox
- Задайте желаемый размер и местоположение
- Установите DropDownStyle в DropDown
- Установите TabStop в значение false
- Установите DisplayMember в значение (я использую список KeyValuePairs)
- Установите ValueMember в Key
- Добавить панель
- Установите тот же размер, что и ComboBox
- Cover ComboBox с панелью (это означает, что стандартный ComboBox выше стандартного TextBox)
- Добавить TextBox
- Поместите TextBox поверх панели
- Выровняйте нижнюю часть TextBox с нижней панелью Panel/ComboBox
Код позади
public partial class TestForm : Form
{
// Custom class for managing calls to an external address finder service
private readonly AddressFinder _addressFinder;
// Events for handling async calls to address finder service
private readonly AddressSuggestionsUpdatedEventHandler _addressSuggestionsUpdated;
private delegate void AddressSuggestionsUpdatedEventHandler(object sender, AddressSuggestionsUpdatedEventArgs e);
public TestForm()
{
InitializeComponent();
_addressFinder = new AddressFinder(new AddressFinderConfigurationProvider());
_addressSuggestionsUpdated += AddressSuggestions_Updated;
}
private void textBox1_PreviewKeyDown(object sender, PreviewKeyDownEventArgs e)
{
if (e.KeyCode == Keys.Tab)
{
comboBox1_SelectionChangeCommitted(sender, e);
comboBox1.DroppedDown = false;
}
}
private void textBox1_KeyDown(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.Up)
{
if (comboBox1.Items.Count > 0)
{
if (comboBox1.SelectedIndex > 0)
{
comboBox1.SelectedIndex--;
}
}
e.Handled = true;
}
else if (e.KeyCode == Keys.Down)
{
if (comboBox1.Items.Count > 0)
{
if (comboBox1.SelectedIndex < comboBox1.Items.Count - 1)
{
comboBox1.SelectedIndex++;
}
}
e.Handled = true;
}
else if (e.KeyCode == Keys.Enter)
{
comboBox1_SelectionChangeCommitted(sender, e);
comboBox1.DroppedDown = false;
textBox1.SelectionStart = textBox1.TextLength;
e.Handled = true;
}
}
private void textBox1_KeyPress(object sender, KeyPressEventArgs e)
{
if (e.KeyChar == '\r') // Enter key
{
e.Handled = true;
return;
}
if (char.IsControl(e.KeyChar) && e.KeyChar != '\b') // Backspace key
{
return;
}
if (textBox1.Text.Length > 1)
{
Task.Run(() => GetAddressSuggestions(textBox1.Text));
}
}
private void comboBox1_SelectionChangeCommitted(object sender, EventArgs e)
{
if (comboBox1.Items.Count > 0 &&
comboBox1.SelectedItem.IsNotNull() &&
comboBox1.SelectedItem is KeyValuePair<string, string>)
{
var selectedItem = (KeyValuePair<string, string>)comboBox1.SelectedItem;
textBox1.Text = selectedItem.Value;
// Do Work with selectedItem
}
}
private async Task GetAddressSuggestions(string searchString)
{
var addressSuggestions = await _addressFinder.CompleteAsync(searchString).ConfigureAwait(false);
if (_addressSuggestionsUpdated.IsNotNull())
{
_addressSuggestionsUpdated.Invoke(this, new AddressSuggestionsUpdatedEventArgs(addressSuggestions));
}
}
private void AddressSuggestions_Updated(object sender, AddressSuggestionsUpdatedEventArgs eventArgs)
{
try
{
ThreadingHelper.BeginUpdate(comboBox1);
ThreadingHelper.ClearItems(comboBox1);
if (eventArgs.AddressSuggestions.Count > 0)
{
foreach (var addressSuggestion in eventArgs.AddressSuggestions)
{
var item = new KeyValuePair<string, string>(addressSuggestion.Key, addressSuggestion.Value.ToUpper());
ThreadingHelper.AddItem(comboBox1, item);
}
ThreadingHelper.SetDroppedDown(comboBox1, true);
ThreadingHelper.SetVisible(comboBox1, true);
}
else
{
ThreadingHelper.SetDroppedDown(comboBox1, false);
}
}
finally
{
ThreadingHelper.EndUpdate(comboBox1);
}
}
private class AddressSuggestionsUpdatedEventArgs : EventArgs
{
public IList<KeyValuePair<string, string>> AddressSuggestions { get; }
public AddressSuggestionsUpdatedEventArgs(IList<KeyValuePair<string, string>> addressSuggestions)
{
AddressSuggestions = addressSuggestions;
}
}
}
У вас могут возникнуть проблемы с настройкой свойства DroppedDown для ComboBox. В конце концов я просто завернул его в блок try с пустым блоком catch. Не отличное решение, но оно работает.
См. Мой другой ответ ниже для информации о ThreadingHelpers.
Наслаждаться.