我的问题与此类似:如何动态更改 C# 组合框或文本框中的自动完成条目? 但我还是没找到解决办法。
问题简述:
我有一个
ComboBox
和大量记录要显示在其中。当用户开始输入时,我想加载以输入文本开头的记录,并为用户提供自动完成功能。
正如上面主题中所述,我无法在 сomboBox_TextChanged
上加载它们,因为我总是覆盖以前的结果并且从未看到它们。
我可以仅使用
ComboBox
来实现此功能吗? (不是 TextBox
或 ListBox
)
我使用此设置:
сomboBox.AutoCompleteMode = AutoCompleteMode.SuggestAppend;
сomboBox.AutoCompleteSource = AutoCompleteSource.CustomSource;
这是我的最终解决方案。它可以很好地处理大量数据。我使用
Timer
来确保用户想要找到当前值。它看起来很复杂,但事实并非如此。
感谢 Max Lambertini 提出这个想法。
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;
// Restore default cursor
Cursor.Current = Cursors.Default;
return;
}
else
{
combobox1.DroppedDown = false;
combobox1.SelectionStart = text.Length;
}
}
这个解决方案不是很酷。因此,如果有人有其他解决方案,请与我分享。
我写了这样的东西......
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)
是的,您当然可以...但需要一些工作才能使其无缝运行。这是我想出的一些代码。请记住,它不使用组合框的自动完成功能,如果您使用它来筛选大量项目,它可能会很慢...
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;
}
}
我发现 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;
}
}
此代码写在您的表单加载上。当用户在组合框中键入字母时,它会显示数据库中的所有旅游。该代码会根据用户的需要自动建议并附加正确的选择。
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();
之前的回复都是缺点。提供自己的版本,并在下拉列表中选择所需的项目:
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;
}
}
}
using (var client = new UserServicesClient())
{
var list = new AutoCompleteStringCollection();
list.AddRange(client.ListNames(query).ToArray());
comboBoxName.AutoCompleteCustomSource = list;
}
这是工作的一大痛苦。 我遇到了很多死胡同,但最终的结果相当简单。 希望它可以对某人有益。它可能需要一点吐槽和抛光,仅此而已。
注意:_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;
}
}
我认为这个答案有点晚了,但可能对某人有用。 algreat的回答对我帮助很大,但对我来说,
TextChanged
事件出了问题,当我按下一个键时会多次触发,因此AutoComplete
太慢并且无法正常工作。
我这样修改代码:
public class ComboFillBox
{
public string Name { get; set; }
public int Value { get; set; }
}
List<ComboFillBox> dataList;
private void cmbComboBox_TextUpdate(object sender, EventArgs e)
{
string strForSearch= cmbComboBox.Text;
if (strForSearch.Length > 0)
{
List<ComboFillBox> searchData = dataList.Where(x=> x.Name.Contains(strForSearch)).ToList();
if (searchData.Count() > 0)
{
cmbComboBox.DataSource = searchData;
cmbComboBox.DroppedDown = true;
}
else
{
cmbComboBox.DroppedDown = false;
}
}
else
{
cmbComboBox.DataSource = dataList;
cmbComboBox.DroppedDown = true;
}
cmbComboBox.DisplayMember = "Name";
cmbComboBox.ValueMember = "Value";
cmbComboBox.Text = strForSearch;
cmbComboBox.SelectionStart = strForSearch.Length;
cmbComboBox.SelectionLength = 0;
}
然后在
SelectionChangeCommitted
事件而不是 SelectedIndexChanged
事件中编写代码。
AutoCompleteMode
应打开 None
并且 DropDownStyle
应打开 DropDown
采取2。我下面的回答并没有让我一直达到预期的结果,但它可能对某些人仍然有用。 组合框的自动选择功能给我带来了很大的痛苦。 这个使用位于组合框顶部的文本框,允许我忽略组合框本身中出现的任何内容,而只响应选择更改事件。
隐藏代码
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;
}
}
}
您可能在设置 ComboBox 的 DroppedDown 属性时遇到问题,也可能没有问题。 我最终只是将它包装在一个带有空 catch 块的 try 块中。 这不是一个很好的解决方案,但它确实有效。
请参阅下面我的其他答案,了解有关 ThreadingHelpers 的信息。
享受吧。