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

53
我在C#中有一个组合框,我想使用自动完成建议,但是我希望能够随着用户的输入更改自动完成条目,因为可能的有效条目数量太多,无法在启动时填充AutoCompleteStringCollection。例如,假设我让用户输入姓名。我有可能的名字列表("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”。 - 钩取 TextUpdate 或 KeyPress 事件,而不是 TextChanged。 - 每次用新的 AutoCompleteStringCollection 替换现有的 AutoCompleteCustomSource。
没有这些方法有所帮助,即使是多种组合。Spence建议我尝试覆盖ComboBox函数,以获取用于自动完成的字符串列表。使用反射器,我在ComboBox类中找到了几个看起来很有前途的方法-GetStringsForAutoComplete()SetAutoComplete(),但它们都是私有的,所以我无法从派生类中访问它们。我无法继续下去。
我试着用TextBox替换ComboBox,因为自动完成接口相同,我发现行为略有不同。使用TextBox,它似乎效果更好,自动完成的附加部分可以正常工作,但建议部分不行-建议框短暂地闪现然后立即消失。
所以我想:“好吧,我将放弃建议功能,只使用追加”,但是当我将AutoCompleteMode设置为追加时,我会收到访问冲突异常。建议也会发生同样的事情-唯一不会引发异常的模式是SuggestAppend,即使建议部分也无法正确地工作。
我认为使用C#托管代码时不可能出现访问冲突异常。Avram建议我使用"lock"来解决这个问题,但我不知道该锁定什么——唯一有SyncRoot成员的是AutoCompleteStringCollection,但锁定它并不能防止访问违规异常。我也尝试过锁定ComboBoxTextBox,但也没有帮助。据我所知,锁只能防止其他锁,因此如果底层代码没有使用锁,则我使用锁也不会有任何影响。
总之,我目前无法使用具有动态自动完成功能的TextBoxComboBox。是否有人了解如何实现这一点?
更新:
我还没有解决这个问题,但我发现了更多信息。也许这些信息会激发其他人想出一个解决方案。
我尝试用TextBox替换ComboBox,因为自动完成接口相同,但我发现行为略有不同。使用TextBox时,它似乎工作得更好,因为自动完成的追加部分正常工作,但建议部分不起作用——建议框短暂地闪烁一下,然后立即消失。

因此我想 “好吧,我可以放弃建议功能只使用“Append”,但是当我将 AutoCompleteMode 设为 Append 时,会出现访问冲突异常。对于Suggest也是一样的情况——唯一不会抛出异常的模式是 SuggestAppend,即使这样 Suggest 部分的行为也不正确。

我曾认为,在使用 C# 托管代码时不可能出现访问冲突异常,但无论如何,问题在于我目前无法使用任何类型的自动完成功能与 TextBoxComboBox 。有没有人知道我怎样才能实现它呢?

更新2:

在尝试了其他各种方法,例如在工作线程中更改自动完成,使用 BeginInvoke() 模拟 PostMessage() 类型的行为后,最终我放弃了,并使用列表框实现了自己的自动完成下拉框。它比内置的更加响应,并且我花费的时间比试图使内置的正常工作所用的时间还要短,因此,对于任何想要此行为的其他人而言,教训就是:你可能最好自己实现它。

15个回答

0

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

诀窍不在于对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);可悲的是,我没有其他的解决方案。 - Jim Cramer
从我偶然发现的其他相关“更新AutocompleteSource”的答案中,建议使用.AddRange而不是循环使用.Add,因为它更高效。 - drzaus

0
if(!textBox3.AutoCompleteCustomSource.Contains(textBox3.Text))
   textBox3.AutoCompleteCustomSource.Add(textBox3.Text);

0
更新: 在这个地方放置锁的主要原因是它起作用了 :) 在使用这个技巧后,我遇到的大多数“神秘异常”都消失了

  1. 像这段代码中的锁定,可以帮助处理异常
  2. 正如您之前提到的,使用文本框问题较少
  3. 在这段代码中,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相同,我已经尝试过了,但会偶尔抛出异常。 - Sam Hopkins
2
那个锁并不能防止异常,因为TextChanged处理程序无论如何都不是可重入的——我已经检查过了。 - Sam Hopkins
另外,我尝试过类似的东西(仅偶尔更改AutoCompleteStringCollection,而不是每个字符都更改),但在某些情况下仍会出现异常。底层代码可能存在竞争条件,因此我不放心在生产环境中使用它。 - Sam Hopkins

0

在尝试了这里提供的所有解决方案(都没有成功)之后,我找到了适合我的解决方案:

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

步骤:

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

希望对某些人有所帮助。


-2
请使用这段代码。
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;
        }

}

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