如何动态更改 C# 组合框或文本框中的自动完成条目?

Posted

技术标签:

【中文标题】如何动态更改 C# 组合框或文本框中的自动完成条目?【英文标题】:How can I dynamically change auto complete entries in a C# combobox or textbox? 【发布时间】:2010-10-05 15:49:29 【问题描述】:

我在 C# 中有一个组合框,我想使用自动完成建议,但是我希望能够在用户键入时更改自动完成条目,因为可能的有效条目太多而无法填充 @ 987654323@在启动时。

举个例子,假设我让用户输入一个名字。我有一个可能的名字列表(“Joe”、“John”)和一个姓氏列表(“Bloggs”、“Smith”),但如果我每个都有一千个,那么这将是一百万个可能的字符串 -太多,无法放入自动完成条目。因此,最初我只想将名字作为建议 ("Joe", "John") ,然后一旦用户输入了名字 ("Joe"),我想删除现有的自动完成条目并替换他们有一个新的集合,包括选择的名字和可能的姓氏(“Joe Bloggs”、“Joe Smith”)。为了做到这一点,我尝试了以下代码:

void InitializeComboBox()

    ComboName.AutoCompleteMode = AutoCompleteMode.SuggestAppend;
    ComboName.AutoCompleteSource = AutoCompleteSource.CustomSource;
    ComboName.AutoCompleteCustomSource = new AutoCompleteStringCollection();
    ComboName.TextChanged += new EventHandler( ComboName_TextChanged );


void ComboName_TextChanged( object sender, EventArgs e )

    string text = this.ComboName.Text;
    string[] suggestions = GetNameSuggestions( text );

    this.ComboQuery.AutoCompleteCustomSource.Clear();
    this.ComboQuery.AutoCompleteCustomSource.AddRange( suggestions );

但是,这不能正常工作。似乎对 Clear() 的调用导致自动完成机制“关闭”,直到下一个字符出现在组合框中,但当然当下一个字符出现时,上面的代码再次调用 Clear(),所以用户永远不会实际上看到了自动完成功能。它还会导致组合框的全部内容被选中,因此在每次按键之间您必须取消选择现有文本,这使其无法使用。如果我删除对 Clear() 的调用,则自动完成工作,但似乎 AddRange() 调用无效,因为我添加的新建议不会出现在自动完成下拉列表中。

我一直在寻找解决方案,并看到了各种建议,但我无法让它们中的任何一个工作 - 自动完成功能似乎被禁用,或者没有出现新字符串。这是我尝试过的事情的清单:

在更改字符串之前调用BeginUpdate(),之后调用EndUpdate()。 对所有现有字符串调用 Remove() 而不是 Clear()。 在我更新字符串时从组合框中清除文本,然后再将其添加回来。 在我更改字符串时将 AutoCompleteMode 设置为“None”,然后将其设置回“SuggestAppend”。 挂钩 TextUpdateKeyPress 事件而不是TextChanged。 每次都将现有的AutoCompleteCustomSource 替换为新的AutoCompleteStringCollection

这些都没有帮助,即使是各种组合。 Spence 建议我尝试覆盖 ComboBox 函数,该函数获取要在自动完成中使用的字符串列表。使用反射器,我在ComboBox 类中发现了一些看起来很有希望的方法——GetStringsForAutoComplete()SetAutoComplete(),但它们都是私有的,因此我无法从派生类访问它们。我受不了了。

我尝试将ComboBox 替换为TextBox,因为自动完成界面相同,但我发现行为略有不同。使用 TextBox 似乎效果更好,因为自动完成的 Append 部分可以正常工作,但 Suggest 部分不能 - 建议框短暂地闪烁,然后立即消失。

所以我想“好吧,我可以不用 Suggest 功能而只使用 Append”,但是当我将 AutoCompleteMode 设置为 Append 时,我得到了访问冲突异常。 Suggest 也会发生同样的事情 - 唯一不会引发异常的模式是 SuggestAppend,即使 Suggest 部分行为不正确。

我认为在使用 C# 托管代码时应该不可能出现访问冲突异常。 Avram 建议我使用“锁定”来解决此问题,但我不知道我应该锁定什么 - 唯一具有 SyncRoot 成员的是 AutoCompleteStringCollection,并且锁定不会阻止访问冲突异常。我还尝试锁定ComboBoxTextBox,但这也无济于事。据我了解,lock 只会阻止其他锁,所以如果底层代码没有使用 lock,那么我使用它不会有任何区别。

所有这一切的结果是,我目前无法使用具有动态自动完成功能的 TextBoxComboBox。有人对我如何实现这一点有任何见解吗?

更新:

我还没有得到这个工作,但我发现了更多。也许其中一些会激发其他人提出解决方案。

我尝试将ComboBox 替换为TextBox,因为自动完成界面相同,但我发现行为略有不同。使用 TextBox 似乎效果更好,因为自动完成的 Append 部分可以正常工作,但 Suggest 部分不能 - 建议框短暂地闪烁,然后立即消失。

所以我想“好吧,我可以不用 Suggest 功能而只使用 Append”,但是当我将 AutoCompleteMode 设置为 Append 时,我得到了访问冲突异常。 Suggest 也会发生同样的事情 - 唯一不会引发异常的模式是 SuggestAppend,即使 Suggest 部分行为不正确。

我认为在使用 C# 托管代码时应该不可能出现访问冲突异常,但无论如何,结果是我目前无法将 TextBoxComboBox 用于任何动态自动完成。有人对我如何实现这一点有任何见解吗?

更新 2:

在尝试了各种其他事情(例如更改工作线程中的自动完成功能,并使用BeginInvoke() 模拟 PostMessage() 类型的行为之后,我终于放弃了,只是使用列表框实现了我自己的自动完成下拉菜单。它比内置的响应速度更快,而且我花在这方面的时间比我试图让内置的工作的时间少,所以任何想要这种行为的人的教训是 - 你可能会更好自己实现。

【问题讨论】:

【参考方案1】:

在尝试了这里提供的所有解决方案(没有成功)之后,我发现了一些对我有用的东西:

private void CellBox_TextChanged(object sender, EventArgs e)

    ((TextBox)sender).TextChanged -= CellBox_TextChanged;
    ((TextBox)dataGridView1.EditingControl).AutoCompleteMode = AutoCompleteMode.None;
    ((TextBox)dataGridView1.EditingControl).AutoCompleteCustomSource = null;                
    aCSC.Clear();
    foreach (string value in Autocompletevalues())
    
        aCSC.Add(value);
    
    ((TextBox)dataGridView1.EditingControl).AutoCompleteCustomSource = aCSC;
    ((TextBox)dataGridView1.EditingControl).AutoCompleteMode = AutoCompleteMode.Suggest;
    ((TextBox)sender).TextChanged += CellBox_TextChanged;

步骤:

禁用事件处理程序 禁用自动完成模式 将源设置为空 更新 AutoCompleteStringCollection (aCSC) 将源设置为更新的 AutoCompleteStringCollection 激活自动完成模式 启用事件处理程序

我希望它可以帮助某人..

【讨论】:

【参考方案2】:

我知道这是一个非常古老的问题,但它今天仍然存在。 我的解决方法是将自动完成模式和源属性设置为“无”并手动更新 KeyUp 事件中的项目。

我确信它很老套,但无论输入数据的速度如何,它对我来说都可以完美运行一段时间,而且我的头发开始重新长出来。

您还可以选择只是建议,还是建议并附加。 我希望它可以帮助某人。

private void comboBox1_KeyUp(object sender, KeyEventArgs e)
    

        if (string.IsNullOrWhiteSpace(comboBox1.Text))
        
            e.Handled = true;
            return;
        
        if (comboBox1.Text.Length < 3)
        
            e.Handled = true;
            return;
        

        if (e.KeyCode == Keys.Down || e.KeyCode == Keys.Up)
        
            e.Handled = true;
            return;
        
        else if (e.KeyCode == Keys.Back)
        
            e.Handled = true;
            return;
        

        string text = comboBox1.Text;

        if (e.KeyCode == Keys.Enter)
        
            comboBox1.DroppedDown = false;
            comboBox1.SelectionStart = text.Length;
            e.Handled = true;
            return;
        

        List<string> LS = Suggestions(comboBox1.Text);

        comboBox1.Items.Clear();
        comboBox1.Items.AddRange(LS.ToArray());

        //If you do not want to Suggest and Append
        //comment the following line to only Suggest
        comboBox1.Focus();

        comboBox1.DroppedDown = true;
        comboBox1.SelectionStart = text.Length;

        //Prevent cursor from getting hidden
        Cursor.Current = Cursors.Default;
        e.Handled = true;
    

【讨论】:

【参考方案3】:

最好的解决方案是使用组合框的事件处理程序。通过使用 textUpdate KeyDown DropDownChangeCommit,你可以模仿自动完成模式,你可以自定义搜索的内容和出现在下拉列表中的内容。

我发现this 的答案很有用,但它是用 Visual c++ 编码的,它是工具条组合框,但 概念是相同的。无论如何,.net 中的 c# 和 c++ 有很大的相似性,理解解决方案应该不是问题。

Customized Autosearch of ToolStripCombobox in Visual C++

【讨论】:

【参考方案4】:

使用此代码

private void dataGridView1_EditingControlShowing(object sender,DataGridViewEditingControlShowingEventArgs e)
    

        if (e.Control is DataGridViewComboBoxEditingControl)
        
            ((ComboBox)e.Control).DropDownStyle = ComboBoxStyle.DropDown;
            ((ComboBox)e.Control).AutoCompleteSource = AutoCompleteSource.ListItems;
            ((ComboBox)e.Control).AutoCompleteMode = System.Windows.Forms.AutoCompleteMode.Suggest;
        


【讨论】:

【参考方案5】:

这对我有用,你不用addRange 到同一个AutoCompleteStringCollection,而是每次都创建一个新的。

form.fileComboBox.TextChanged += (sender, e) => 
    var autoComplete = new AutoCompleteStringCollection();
    string[] items = CustomUtil.GetFileNames();
    autoComplete.AddRange(items);
    form.fileComboBox.AutoCompleteCustomSource = autoComplete;
;

【讨论】:

【参考方案6】:

我遇到了同样的问题,并找到了一个非常简单的解决方法。和这里的其他人一样,我找不到任何方法来控制组件的行为,所以我不得不接受它。

自然行为是:您不能在用户每次在文本框中输入内容时动态填充列表。您必须填充一次,然后自动完成机制获得控制权。结论是:您应该使用数据库中所有可能的条目来填充 AutoCompleteCustomSource 以使其按我们想要的方式工作。

如果您有数百万条记录要填充列表,这当然是不可行的。数据传输中的性能问题和自动完成机制本身不允许您这样做。

我找到的折衷解决方案是:每次文本长度恰好达到 N 个字符(在我的情况下为 3 个)时动态填充 AutoCompleteCustomSource。这很有效,因为复杂性大大降低了。从数据库中提取的与这 3 个初始字符匹配的记录数量足够少,可以避免任何性能问题。

主要缺点是:用户在输入第 N 个字符之前不会看到自动完成列表。但在输入 3 个字符之前,用户似乎并不真正期望有一个有意义的自动完成列表。

希望这会有所帮助。

【讨论】:

你确定这行得通吗?在运行时更改 AutoCompleteCustomSource 对我来说根本不起作用。 输入超过 3 个时,源是否会变得更精细?当您开始清除字符时,源是否再次获得更多项目,例如带退格键?【参考方案7】:

对我来说,秘密是使用 TextChanged 事件,而没有使用 KeyDown/Up/Press 等。

更新:在动态更改 AutoCompleteCustomSource 遇到其他问题后,我最终放弃了使用内置的自动完成功能,并在比我最初浪费的时间更短的时间内实现了我自己的功能。在实现 ComboBox 控件的非托管代码中似乎存在一些问题。具体来说,我在应该触发 TextChanged 事件处理程序时遇到了问题。我决定在我的自定义实现中只使用 OnKeyDown/Press/Up 处理程序,这似乎更可靠。

【讨论】:

我已将 TextChange 事件用于我的自动完成模式。但是如何在其中搜索子字符串。例如。如果我输入“Thom”,它应该建议“Thomas”、“Dthomas”以及“Dr. Thomas”。任何帮助【参考方案8】:

没有尝试过,但对于您的具体情况,您可以编写如下代码:

    private void txtAutoComplete_KeyUp(object sender, KeyEventArgs e)
    

        String text = txtAutoComplete.Text;

        if (text.EndsWith(" "))
        

            string[] suggestions = GetNameSuggestions( text ); //put [text + " "] at the begin of each array element
            txtAutoComplete.AutoCompleteCustomSource.Clear();
            txtAutoComplete.AutoCompleteCustomSource.AddRange( suggestions );

        

    

【讨论】:

【参考方案9】:

我最初来这里是为了寻找解决方案,但现在找到了自己的解决方案。

诀窍不是在 AutoCompleteCustomSource 上调用 Clear(),而是在 for 循环中删除所有项目,然后用新数据重建列表。就我而言(一个图书收藏应用程序),我正在从具有特定起始字母的数据库中检索作者姓名,而不是全部。请注意,这仅在组合框的文本框部分为空或已变为空时才有效。

    private void cboAuthor_KeyDown(object sender, KeyEventArgs e)
    
        if (cboAuthor.Text.Length == 0)
        
            // Next two lines simple load data from the database in the
            // into a collection (var gateway), base on first letter in
            // the combobox. This is specific to my app.
            var gateway = new AuthorTableGateway();
            gateway.LoadByFirstLetter(Char.ConvertFromUtf32(e.KeyValue)[0]);

            // Clear current source without calling Clear()
            for (int i = 0; i < authorsAutoComplete.Count; i++)
                authorsAutoComplete.RemoveAt(0);

            // Rebuild with new data
            foreach (var author in gateway)
                authorsAutoComplete.Add(author.AuthorName);
        
    

【讨论】:

进一步的测试证明了这个解决方案显示出不稳定的行为,即使在纠正了错误的“清除”循环之后应该是:while (authorsAutoComplete.Count > 0) authorsAutoComplete.RemoveAt(0);可悲的是,我没有其他解决方案。 从我偶然发现的其他相关“更新 AutocompleteSource”答案中,建议使用.AddRange 而不是循环使用.Add,因为它更高效。【参考方案10】:
if(!textBox3.AutoCompleteCustomSource.Contains(textBox3.Text))
   textBox3.AutoCompleteCustomSource.Add(textBox3.Text);

【讨论】:

【参考方案11】:

山姆,你搞清楚了吗?我遇到了同样的情况。 Clear() 似乎导致异常。我删除了 clear 调用,并且我收到了正确的建议事件,尽管集合不断增长...

另外,关于私有成员:您可以使用反射访问它们:

PropertyInfo[] props = [object].GetType().GetProperties(flags go here);
props[0].SetValue(this, new object[]  0 );

【讨论】:

【参考方案12】:

更新: 把锁放在这个地方的主要原因是

它的工作:) 在这个技巧之后,我曾经拥有的大部分“神秘异常”消失


    此代码中的锁可以帮助您解决异常 正如您之前提到的,使用文本框的问题较少 在此代码中,SuggestAppend 工作正常

    private void Form1_Load(object sender, EventArgs e)
    
        textBox1.AutoCompleteMode = AutoCompleteMode.SuggestAppend;
        textBox1.AutoCompleteSource = AutoCompleteSource.CustomSource;

        textBox1.TextChanged+=new EventHandler(textBox1_TextChanged);

        col1.AddRange(new string[]  "avi avi", "avram avram" );
        col2.AddRange(new string[]  "boria boria", "boris boris" );

        textBox1.AutoCompleteCustomSource = col1;
        textBox1.AutoCompleteMode = AutoCompleteMode.SuggestAppend;
    
    AutoCompleteStringCollection col1 = new AutoCompleteStringCollection();
    AutoCompleteStringCollection col2 = new AutoCompleteStringCollection();

    object locker = new object();
    private void textBox1_TextChanged(object sender, EventArgs e)
    
        lock (locker)
        
            if (textBox1.Text.StartsWith("a") && textBox1.AutoCompleteCustomSource != col1)
            
                textBox1.AutoCompleteCustomSource = col1;
            
            if (textBox1.Text.StartsWith("b") && textBox1.AutoCompleteCustomSource != col2)
            
                textBox1.AutoCompleteCustomSource = col2;
            
        
    

【讨论】:

我没有尝试过确切的代码,但我认为它与每次用新的 AutoCompleteStringCollection 替换现有的 AutoCompleteCustomSource 基本相同,我尝试过并且偶尔会引发异常。 该锁不会阻止异常,因为 TextChanged 处理程序无论如何都不会重入 - 我已经检查过了。 另外,我尝试过类似的方法(只是偶尔更改 AutoCompleteStringCollection,而不是在每个字符上),但在某些情况下我仍然会遇到异常。底层代码中可能存在竞争条件,所以我不习惯在生产环境中使用它【参考方案13】:

我没有对此进行测试,但可能值得一试。

不是清除 AutoCompleteCustomSource,而是通过保留两个实例来增加缓冲区。当文本更改时,调用 GetNameSuggestions() 并为当前未使用的字符串构建字符串,然后将 ComboName.AutoCompleteCustomSource 设置为您刚刚设置的字符串。

我认为它应该看起来像这样。

AutoCompleteCustomSource accs_a;
AutoCompleteCustomSource accs_b;
bool accs_check = true; //true for accs_a, false for accs_b
void InitializeComboBox()

    ComboName.AutoCompleteMode = AutoCompleteMode.SuggestAppend;
    ComboName.AutoCompleteSource = AutoCompleteSource.CustomSource;

    accs_a = new AutoCompleteStringCollection();
    accs_b = new AutoCompleteStringCollection();

    ComboName.AutoCompleteCustomSource = accs_a;
    ComboName.TextChanged += new EventHandler( ComboName_TextChanged );


void ComboName_TextChanged( object sender, EventArgs e )

    string text = this.ComboName.Text;

    if(accs_check)
    
       accs_b.Clear();
       accs_b.AddRange(GetNameSuggestions( text ));
       accs_check = false;
    
    else
    
       accs_a.Clear();
       accs_a.AddRange(GetNameSuggestions( text ));
       accs_check = true;
    

    this.ComboQuery.AutoCompleteCustomSource = accs_check? accs_a : accs_b;

【讨论】:

【参考方案14】:

我认为您可能想要退出反射器并查看覆盖组合框本身的自动完成行为。我确定自动完成会调用一个访问自动完成列表的函数。如果你能找到这个函数并覆盖它,你就可以使用任何你想要的行为。

查看您可以在组合框类本身上找到哪些文档。

【讨论】:

以上是关于如何动态更改 C# 组合框或文本框中的自动完成条目?的主要内容,如果未能解决你的问题,请参考以下文章

如何在文本框或组合框中显示多个值

从C#中的WPF组合框或文本框获取文本

从自动完成中选择的事件

如何动态更改组合框显示成员

在 Blazor 中的文本或数字框中自动突出显示

为啥动态值没有填充在自动完成组合框中?