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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/11780558/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-09 19:13:58  来源:igfitidea点击:

C# winforms combobox dynamic autocomplete

c#winformsdynamicautocompletecombobox

提问by algreat

My problem is similar to this one: How can I dynamically change auto complete entries in a C# combobox or textbox?But I still don't find solution.

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

The problem briefly:

问题简述:

I have an ComboBoxand a large number of records to show in it. When user starts typing I want to load records that starts with input text and offer the user for autocomplete. As described in the topic above I can't load them on сomboBox_TextChangedbecause I always overwrite the previous results and never see them.

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

Can I implement this using only ComboBox? (not TextBoxor ListBox)

我可以只使用ComboBox吗?(不是TextBoxListBox

I use this settings:

我使用这个设置:

сomboBox.AutoCompleteMode = AutoCompleteMode.SuggestAppend;
сomboBox.AutoCompleteSource = AutoCompleteSource.CustomSource;

采纳答案by algreat

Here is my final solution. It works fine with a large amount of data. I use Timerto make sure the user want find current value. It looks like complex but it doesn't. Thanks to Max Lambertinifor the idea.

这是我的最终解决方案。它适用于大量数据。我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;


                return;
            }
            else
            {
                combobox1.DroppedDown = false;
                combobox1.SelectionStart = text.Length;
            }
        }

This solution isn't very cool. So if someone has another solution please share it with me.

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

回答by Max Lambertini

Yes, you surely can... but it needs some work to make it work seamlessly. This is some code I came up with. Bear in mind that it does notuse combobox's auto-complete features, and it might be quite slow if you use it to sift thru a lot of items...

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

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

回答by shoaib shaikh

This code is write on your form load. It display all the Tour in database when user type letter in combo box. This code automatically suggest and append the right choice as user want.

此代码写在您的表单加载上。当用户在组合框中输入字母时,它会显示数据库中的所有 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();

回答by knatz

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

回答by VadimSm

In previous replies are drawbacks. Offers its own version with the selection in the drop down list the desired item:

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

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

        }
    }

回答by JamesFaix

I've found Max Lambertini's answer very helpful, but have modified his HandleTextChanged method as such:

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

回答by Tonky75

I wrote something like this ....

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

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

That's all (Y)

仅此而已 (Y)

回答by Devanathan.S

I Also come across these kind of requirements recently.I set the below properties with out writing the code it works.see if this helps you.

我最近也遇到了这些需求。我设置了以下属性而没有编写它的工作代码。看看这是否对你有帮助。

enter image description here

在此处输入图片说明

回答by kraeg

This was a major pain to get working. I hit a bunch of dead ends, but the final result is reasonably straight forward. Hopefully it can be of benefit to someone. It may need a little spit and polish that's all.

这是开始工作的主要痛苦。我遇到了一堆死胡同,但最终结果相当直接。希望它可以对某人有益。它可能需要一点点吐痰和润色,仅此而已。

Note: _addressFinder.CompleteAsync returns a list of KeyValuePairs.

注意:_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 is just a set of static methods of the form:

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

回答by kraeg

Take 2. My answer below didn't get me all the way to the desired result, but it may still be useful to somebody. The auto-select feature of the ComboBox was causing me major pain. This one uses a TextBox sitting over the top of a ComboBox, allowing me to ignore whatever appears in the ComboBox itself and just respond to the selection changed event.

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

  1. Create Form
  2. Add ComboBox
    • Set desired size and location
    • Set DropDownStyle to DropDown
    • Set TabStop to false
    • Set DisplayMember to Value(I'm using a list of KeyValuePairs)
    • Set ValueMember to Key
  3. Add Panel
    • Set to same size as ComboBox
    • Cover ComboBox with the Panel (This accounts for the standard ComboBox being taller than the standard TextBox)
  4. Add TextBox
    • Place TextBox over the top of the Panel
    • Align bottom of the TextBox with the bottom of Panel/ComboBox
  1. 创建表格
  2. 添加组合框
    • 设置所需的大小和位置
    • 将 DropDownStyle 设置为DropDown
    • 将 TabStop 设置为false
    • 将 DisplayMember 设置为Value(我使用的是 KeyValuePairs 列表)
    • 将 ValueMember 设置为Key
  3. 添加面板
    • 设置为与 ComboBox 相同的大小
    • 用面板覆盖 ComboBox(这说明标准 ComboBox 比标准 TextBox 高)
  4. 添加文本框
    • 将 TextBox 放在面板的顶部
    • 将 TextBox 的底部与 Panel/ComboBox 的底部对齐

Code behind

背后的代码

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

You may or may not have issues with setting the DroppedDown property of the ComboBox. I eventually just wrapped it up in a try block with an empty catch block. Not a great solution, but it works.

您在设置 ComboBox 的 DroppedDown 属性时可能有也可能没有问题。我最终只是将它包裹在一个带有空 catch 块的 try 块中。不是一个很好的解决方案,但它有效。

Please see my other answer below for info on ThreadingHelpers.

有关 ThreadingHelpers 的信息,请参阅下面我的其他答案。

Enjoy.

享受。