C#窗体中的下拉框动态自动完成

39
我的问题类似于这个: 如何在C#的ComboBox或TextBox中动态更改自动完成条目? 但我仍然找不到解决方案。
问题简述:
我有一个 ComboBox 和大量记录要显示在其中。当用户开始输入时,我想加载以输入文本开头的记录,并为用户提供自动完成选项。 如上所述,在 сomboBox_TextChanged 中无法加载它们,因为我始终覆盖先前的结果并从未看到它们。 我能否仅使用 ComboBox 实现此功能?(而不使用 TextBox 或 ListBox)
我使用以下设置:
сomboBox.AutoCompleteMode = AutoCompleteMode.SuggestAppend;
сomboBox.AutoCompleteSource = AutoCompleteSource.CustomSource;

7
嗯...它在WinForm上。 - algreat
抱歉,我太专注于Web了,但概念是相同的。有一个监听按键的事件并调用一个方法,该方法接受字符串并返回结果集。 - Brian
1
问题在于没有必要的事件。我需要像comboBox_TextChanging这样的东西。我尝试使用comboBox_TextUpdate和comboBox_KeyDown,但两者都有不同的问题。 - algreat
11个回答

72

我最近也遇到了这类需求。我设置了以下属性,而不必编写代码,它就可以工作。看看这是否对你有帮助。

自动完成模式:建议附加//自动完成源:列表项//下拉框样式:下拉列表


11
我喜欢这个解决方案。我所做的一项更改是,我选择了DropDownStyle=DropDown,这样用户可以开始自由输入,然后从列表中自动完成。 - Der Wolf

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;


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

这个解决方案并不是很好。如果有其他的解决方案,请与我分享。


8
如果你和我一样发现使用“combobox1.DroppedDown = true”会导致鼠标光标消失,那么在它后面添加“Cursor.Current = Cursors.Default”即可解决问题。 - Andy
“Search”这个名称在当前上下文中不存在。我错过了什么? - YıldızGezen
1
搜索是用户定义的函数,用于获取数据源。 - Taja_100
1
你在处理定时器的时候是使用“节流防抖”对吧?你可以从Rick Strahl的网站上使用“防抖调度程序”,以摆脱所有定时器逻辑/复杂性。 - bh_earth0

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

这是一个好的解决方案。它比默认的自动完成功能更好。但对于大量数据还不够。所以我会尝试改进你的解决方案。 - algreat
1
只有当文本长度超过三、四个字符时,您才可以通过在数据上启动查询来触发自动完成。 - Max Lambertini
使用超时功能可能是个不错的选择,当用户停止输入一段预定义时间后,程序自动开始执行查询。 - Jose Rodriguez
@Max - 小心啊,我曾经因为这种方法被烧过:数据库中的一些姓氏是“李”,而我只用了3个字符! :) - Frank Monroe

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

之前的回复中存在一些缺点。我提供了自己的版本,可以在下拉列表中选择所需项目:

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

        }
    }

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

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

2
通常,如果答案包括代码的用途和为什么这样做可以解决问题并且不会引入其他问题的解释,则这些答案会更加有帮助。(此帖子已被至少一位用户标记,可能是因为他们认为没有解释的答案应该被删除。) - Nathan Tuggy

0

这真是一项艰巨的工作。我遇到了很多难题,但最终结果相对来说还是比较简单明了的。希望它能对某些人有所帮助。可能需要稍加修改和完善。

注意:_addressFinder.CompleteAsync返回一个KeyValuePair列表。

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。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接