如何在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个回答

13

我也遇到了同样的问题,不过找到了一个极其简单的解决方法。和这里的其他人一样,我找不到任何控制组件行为的方法,所以只能接受现状。

自然的行为是:当用户输入文本时,无法动态填充列表。您必须先填充它,然后让自动完成机制接管。结论是:为了使其按我们想要的方式工作,应该将AutoCompleteCustomSource填充为数据库中的每个可能的条目。

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

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

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

希望这可以帮到您。


1
你确定这个能正常工作吗?在运行时更改AutoCompleteCustomSource在我的端上根本不起作用。 - Graviton
当您输入超过3个字符时,源代码是否会变得更加精细? 当您开始使用退格键清除字符时,源代码是否会再次出现更多项? - MarioDS

4
这对我有用,你不需要将addRange添加到同一个AutoCompleteStringCollection中,而是每次创建一个新的。
form.fileComboBox.TextChanged += (sender, e) => {
    var autoComplete = new AutoCompleteStringCollection();
    string[] items = CustomUtil.GetFileNames();
    autoComplete.AddRange(items);
    form.fileComboBox.AutoCompleteCustomSource = autoComplete;
};

1

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

看看你能找到关于组合框类本身的文档。


1

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

不要清除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;
}

0

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

我确定这很hacky,但对我来说完美地工作了相当长一段时间,无论数据输入的速度如何,还有额外的好处是我的头发开始长回来了。

您还可以选择只建议或建议并附加。 我希望它能帮助到某些人。

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

0

虽然我没有尝试过这个,但是针对您的具体情况,您可以编写类似以下代码:

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

        }

    }

0

Sam,你解决了这个问题吗?我也遇到了同样的情况。调用Clear()似乎会导致异常。我删除了对clear的调用,尽管集合不断增长,但我仍然得到了正确的建议...

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

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

0

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

我发现this的答案很有用,但它是用Visual C++编写的,而且是ToolStripComboBox,但概念是相同的。无论如何,在.NET中,C#和C++之间存在巨大的相似性,理解解决方案不应该是问题。

在Visual C++中自定义ToolStripComboBox的自动搜索


0

我尝试了所有的解决方案都没有成功,还遇到了崩溃(AccessViolation)问题,并开始寻找可行的解决方案。我找到了一种动态工作的方法,并在我的博客中进行了解释:

Winforms文本框 - 动态自动完成

你需要对TextBox和AutoCompleteStringCollection进行子类化。


0

对我来说,秘诀在于使用TextChanged事件,而不是KeyDown/Up/Press等事件。

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


我已经使用了TextChange事件来实现自动完成模式。但是如何在其中搜索子字符串呢?例如,如果我输入“Thom”,它应该建议“Thomas”、“Dthomas”以及“Dr. Thomas”。有什么帮助吗? - Sandy

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