C# winforms 组合框动态自动完成

问题描述 投票:0回答:11

我的问题与此类似:如何动态更改 C# 组合框或文本框中的自动完成条目? 但我还是没找到解决办法。

问题简述:

我有一个

ComboBox
和大量记录要显示在其中。当用户开始输入时,我想加载以输入文本开头的记录,并为用户提供自动完成功能。 正如上面主题中所述,我无法在
сomboBox_TextChanged
上加载它们,因为我总是覆盖以前的结果并且从未看到它们。

我可以仅使用

ComboBox
来实现此功能吗? (不是
TextBox
ListBox

我使用此设置:

сomboBox.AutoCompleteMode = AutoCompleteMode.SuggestAppend;
сomboBox.AutoCompleteSource = AutoCompleteSource.CustomSource;
c# winforms dynamic autocomplete combobox
11个回答
77
投票

我最近也遇到了这样的需求。 我设置了以下属性而不编写代码并且它有效。 看看这是否对你有帮助。

AutoCompleteMode: SuggestAppend // AutoCompleteSource: ListItems // DropDownStyle: DropDownList


26
投票

这是我的最终解决方案。它可以很好地处理大量数据。我使用

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

这个解决方案不是很酷。因此,如果有人有其他解决方案,请与我分享。


11
投票

我写了这样的东西......

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)


10
投票

是的,您当然可以...但需要一些工作才能使其无缝运行。这是我想出的一些代码。请记住,它使用组合框的自动完成功能,如果您使用它来筛选大量项目,它可能会很慢...

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

3
投票

我发现 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;
        }
    }

2
投票

此代码写在您的表单加载上。当用户在组合框中键入字母时,它会显示数据库中的所有旅游。该代码会根据用户的需要自动建议并附加正确的选择。

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

2
投票

之前的回复都是缺点。提供自己的版本,并在下拉列表中选择所需的项目:

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

        }
    }

0
投票
 using (var client = new UserServicesClient())
 {
     var list = new AutoCompleteStringCollection();
     list.AddRange(client.ListNames(query).ToArray());
     comboBoxName.AutoCompleteCustomSource = list;
 }

0
投票

这是工作的一大痛苦。 我遇到了很多死胡同,但最终的结果相当简单。 希望它可以对某人有益。它可能需要一点吐槽和抛光,仅此而已。

注意:_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;
        }
    }

0
投票

我认为这个答案有点晚了,但可能对某人有用。 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
投票

采取2。我下面的回答并没有让我一直达到预期的结果,但它可能对某些人仍然有用。 组合框的自动选择功能给我带来了很大的痛苦。 这个使用位于组合框顶部的文本框,允许我忽略组合框本身中出现的任何内容,而只响应选择更改事件。

  1. 创建表格
  2. 添加组合框
    • 设置所需的尺寸和位置
    • 将 DropDownStyle 设置为 DropDown
    • 将 TabStop 设置为 false
    • 将 DisplayMember 设置为 Value(我使用的是 KeyValuePairs 列表)
    • 将 ValueMember 设置为 Key
  3. 添加面板
    • 设置为与 ComboBox 相同的大小
    • 用面板覆盖 ComboBox(这说明标准 ComboBox 比标准 TextBox 高)
  4. 添加文本框
    • 将文本框放在面板顶部
    • 将文本框的底部与面板/组合框的底部对齐

隐藏代码

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 的信息。

享受吧。

© www.soinside.com 2019 - 2024. All rights reserved.