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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/515561/
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-04 06:25:06  来源:igfitidea点击:

How can I dynamically change auto complete entries in a C# combobox or textbox?

c#winformsautocompletecomboboxtextbox

提问by

I have a combobox in C# and I want to use auto complete suggestions with it, however I want to be able to change the auto complete entries as the user types, because the possible valid entries are far too numerous to populate the AutoCompleteStringCollectionat startup.

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

As an example, suppose I'm letting the user type in a name. I have a list of possible first names ("Joe", "John") and a list of surnames ("Bloggs", "Smith"), but if I have a thousand of each, then that would be a million possible strings - too many to put in the auto complete entries. So initially I want to have just the first names as suggestions ("Joe", "John") , and then once the user has typed the first name, ("Joe"), I want to remove the existing auto complete entries and replace them with a new set consisting of the chosen first name followed by the possible surnames ("Joe Bloggs", "Joe Smith"). In order to do this, I tried the following code:

举个例子,假设我让用户输入一个名字。我有一个可能的名字列表(“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 );
}

However, this does not work properly. It seems that the call to Clear() causes the auto complete mechanism to "turn off" until the next character appears in the combo box, but of course when the next character appears the above code calls Clear() again, so the user never actually sees the auto complete functionality. It also causes the entire contents of the combo box to become selected, so between every keypress you have to deselect the existing text, which makes it unusable. If I remove the call to Clear() then the auto complete works, but it seems that then the AddRange()call has no effect, because the new suggestions that I add do not appear in the auto complete dropdown.

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

I have been searching for a solution to this, and seen various things suggested, but I cannot get any of them to work - either the auto complete functionality appears disabled, or new strings do not appear. Here is a list of things I have tried:

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

  • Calling BeginUpdate()before changing the strings and EndUpdate()afterward.
  • Calling Remove()on all the existing strings instead of Clear().
  • Clearing the text from the combobox while I update the strings, and adding it back afterward.
  • Setting the AutoCompleteModeto "None" while I change the strings, and setting it back to "SuggestAppend" afterwards.
  • Hooking theTextUpdateor KeyPressevent instead of TextChanged.
  • Replacing the existing AutoCompleteCustomSourcewith a new AutoCompleteStringCollectioneach time.
  • BeginUpdate()在更改字符串之前和EndUpdate()之后调用。
  • 调用Remove()所有现有字符串而不是 Clear()。
  • 在更新字符串时清除组合框中的文本,然后将其添加回来。
  • AutoCompleteMode在更改字符串时将 设置为“无”,然后将其设置回“SuggestAppend”。
  • 挂钩TextUpdateKeyPress事件而不是TextChanged.
  • 每次都AutoCompleteCustomSource用新的替换现有的AutoCompleteStringCollection

None of these helped, even in various combinations. Spencesuggested that I try overriding the ComboBoxfunction that gets the list of strings to use in auto complete. Using a reflector I found a couple of methods in the ComboBoxclass that look promising - GetStringsForAutoComplete()and SetAutoComplete(), but they are both private so I can't access them from a derived class. I couldn't take that any further.

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

I tried replacing the ComboBoxwith a TextBox, because the auto complete interface is the same, and I found that the behaviour is slightly different. With the TextBoxit appears to work better, in that the Append part of the auto complete works properly, but the Suggest part doesn't - the suggestion box briefly flashes to life but then immediately disappears.

我尝试用ComboBoxa替换TextBox,因为自动完成界面是相同的,我发现行为略有不同。随着TextBox它似乎更好地工作,在自动完成工作正常的追加一部分,但建议部分不-的建议箱简要闪烁的生命,但随即消失。

So I thought "Okay, I'll live without the Suggest functionality and just use Append instead", however when I set the AutoCompleteModeto Append, I get an access violation exception. The same thing happens with Suggest - the only mode that doesn't throw exceptions is SuggestAppend, even though the Suggest part doesn't then behave correctly.

所以我想“好吧,我会在没有 Suggest 功能的情况下生活,而只是使用 Append”,但是当我将 设置AutoCompleteMode为 Append 时,我会收到访问冲突异常。同样的事情发生在 Suggest - 唯一不抛出异常的模式是SuggestAppend,即使 Suggest 部分的行为不正确。

I thought that it was supposed to be impossible to get access violation exceptions when using C# managed code. Avramsuggested I use "lock" to fix this, but I don't know what I should lock - the only thing that has a SyncRoot member is the AutoCompleteStringCollection, and locking that doesn't prevent the access violation exceptions. I also tried locking the ComboBoxor TextBox, but that didn't help either. As I understand it, lock only prevents other locks, so if the underlying code isn't using lock then my using it won't make any difference.

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

The upshot of all this is that I can't currently use a TextBoxor a ComboBoxwith dynamic auto complete. Does anyone have any insights into how I could achieve this?

所有这一切的结果是我目前不能使用带有动态自动完成功能的aTextBox或 a ComboBox。有没有人对我如何实现这一目标有任何见解?

Update:

更新:

I still haven't got this working, but I have found out some more. Maybe some of this will inspire someone else to come up with a solution.

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

I tried replacing the ComboBoxwith a TextBox, because the auto complete interface is the same, and I found that the behaviour is slightly different. With the TextBoxit appears to work better, in that the Append part of the auto complete works properly, but the Suggest part doesn't - the suggestion box briefly flashes to life but then immediately disappears.

我尝试用ComboBoxa替换TextBox,因为自动完成界面是相同的,我发现行为略有不同。随着TextBox它似乎更好地工作,在自动完成工作正常的追加一部分,但建议部分不-的建议箱简要闪烁的生命,但随即消失。

So I thought "Okay, I'll live without the Suggest functionality and just use Append instead," however when I set the AutoCompleteModeto Append, I get an access violation exception. The same thing happens with Suggest - the only mode that doesn't throw exceptions is SuggestAppend, even though the Suggest part doesn't then behave correctly.

所以我想“好吧,我会在没有 Suggest 功能的情况下生活,而只是使用 Append”,但是当我将 设置AutoCompleteMode为 Append 时,我会收到访问冲突异常。同样的事情发生在 Suggest - 唯一不抛出异常的模式是SuggestAppend,即使 Suggest 部分的行为不正确。

I thought that it was supposed to be impossible to get access violation exceptions when using C# managed code, but anyway, the upshot is that I can't currently use a TextBoxor a ComboBoxwith any kind of dynamic auto complete. Does anyone have any insights into how I could achieve this?

我认为在使用 C# 托管代码时应该不可能获得访问冲突异常,但无论如何,结果是我目前无法将 aTextBox或 aComboBox与任何类型的动态自动完成一起使用。有没有人对我如何实现这一目标有任何见解?

Update 2:

更新 2:

After trying various other things such as changing the autocomplete in a worker thread, and using BeginInvoke()to simulate PostMessage() type behaviour, I finally gave up and just implemented my own auto complete dropdown using a list box. It's much more responsive than the built-in one, and I spent less time doing that than I did trying to get the built-in one to work, so the lesson for anyone else who wants this behaviour is - you're probably better off implementing it yourself.

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

回答by Spence

I think you might want to get out the reflector and look at overriding the autocomplete behaviour in the combobox itself. I'm certain the autocompletion would call a function which accesses the autocomplete list. If you can find this function and override it, you can use any behaviour you want.

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

See what documentation you can find on the combobox class itself.

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

回答by Adam Haile

I haven't test this, but it may be worth a shot.

我没有测试过这个,但它可能值得一试。

Instead of clearing the AutoCompleteCustomSource, double buffer by keeping two instances. When the text changes, call GetNameSuggestions() and build the strings for the one that's not currently being used, then set ComboName.AutoCompleteCustomSource to the one you just set up.

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

I think it should look something like this.

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

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

回答by Avram

update: main reason to put the lock on this place is

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

its working :) most of "mysterious exception" that i ever have, after this trick disappear

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



  1. the lock like in this code, can help with your exception
  2. as you mention before, there is less problem with using textbox
  3. in this code, SuggestAppend working fine
  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;
            }
        }
    }

回答by Avram

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

回答by fixitchris

Sam, have you got this figured out? I am running into the same situation. Clear() seems to cause the exception. I removed the call to clear and I'm getting the correct suggestions event though the collection keeps on growing...

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

Also, regarding the private members: you can access them using reflection:

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

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

回答by Jim Cramer

I came here initially looking for a solution, but have now found my own.

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

The trick is not to call Clear() on the AutoCompleteCustomSource but to remove all items in a for loop and then rebuild the list with the new data. In my case (a book collection application) I'm retrieving author names from a database with a specific starting letter, instead of the whole lot. Note that this will only work if the textbox part of the combobox is or has become empty.

诀窍不是在 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);
        }
    }

回答by Luca Fagioli

Haven't try this, but for your specific case you could code something like:

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

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

        }

    }

回答by Boog

For me the secret was using the TextChanged event and none of the KeyDown/Up/Press, etc.

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

Update:After having other issues with dynamically changing the AutoCompleteCustomSource I eventually abandoned using the builtin Autocomplete functionality and implemented my own in much shorter time than I had wasted on it originally. There seems to be some issues in unmanaged code that implements the ComboBox control. Specifically, I was having issues with the TextChanged event handler firing when it should. I decided to only use the OnKeyDown/Press/Up handlers in my custom implementation and that seemed to be more reliable.

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

回答by Alexandre Mafra

I had the same problem, and found an extremely simple workaround. As everybody else here, I couldn't find any means to control de behaviour of the component, so I had to accept it.

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

The natural behaviour is: you can't dynamically populate the list every time the user types into the text box. You have to populate it once, and then the AutoComplete mechanism takes control. The conclusion is: you should populate the AutoCompleteCustomSource with every possible entry in you database to make it work as we want.

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

Of course this is not viable if you have millions of records to populate the list. Performance issues in data transfer and the AutoComplete mechanism itself will not allow you to do that.

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

The compromise solution I found was: dynamically populate the AutoCompleteCustomSource every time that the Text length reaches exactly N chars (3 in my case). This worked because complexity was drastically reduced. The number of records that are fetched from the database that match these 3 initial chars was small enough to avoid any performance issues.

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

The major drawback is: users will not be presented the AutoComplete list until they type the N-th char. But it seems like users don't really expect a meaningful AutoComplete list before 3 chars are typed.

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

Hope this helps.

希望这可以帮助。

回答by Jaanus

This worked for me, you don't addRangeto the same AutoCompleteStringCollection, but rather create a new one each time.

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

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