50

我在 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”。
  • 挂钩 TextUpdateorKeyPress事件而不是TextChanged.
  • 每次都AutoCompleteCustomSource用新的替换现有的。AutoCompleteStringCollection

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

我尝试用 替换ComboBoxTextBox因为自动完成界面是相同的,我发现行为略有不同。它似乎工作得更好,因为TextBox自动完成的附加部分工作正常,但建议部分没有 - 建议框短暂地闪烁,然后立即消失。

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

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

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

更新:

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

我尝试用 替换ComboBoxTextBox因为自动完成界面是相同的,我发现行为略有不同。它似乎工作得更好,因为TextBox自动完成的附加部分工作正常,但建议部分没有 - 建议框短暂地闪烁,然后立即消失。

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

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

更新 2:

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

4

14 回答 14

13

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

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

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

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

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

希望这可以帮助。

于 2011-12-06T06:38:26.747 回答
3

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

form.fileComboBox.TextChanged += (sender, e) => {
    var autoComplete = new AutoCompleteStringCollection();
    string[] items = CustomUtil.GetFileNames();
    autoComplete.AddRange(items);
    form.fileComboBox.AutoCompleteCustomSource = autoComplete;
};
于 2013-06-29T09:14:12.467 回答
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;
}
于 2009-02-05T18:01:05.060 回答
1

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

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

于 2009-02-05T11:51:34.883 回答
0

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


    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;
            }
        }
    }
于 2009-02-07T21:54:25.400 回答
0

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

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

Visual C++ 中 ToolStripCombobox 的自定义自动搜索

于 2015-01-07T08:23:28.377 回答
0
if(!textBox3.AutoCompleteCustomSource.Contains(textBox3.Text))
   textBox3.AutoCompleteCustomSource.Add(textBox3.Text);
于 2009-04-28T19:12:44.377 回答
0

山姆,你搞清楚了吗?我遇到了同样的情况。Clear() 似乎导致异常。我删除了清除呼叫,尽管收藏不断增长,但我得到了正确的建议事件......

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

PropertyInfo[] props = [object].GetType().GetProperties({flags go here});
props[0].SetValue(this, new object[] { 0 });
于 2010-04-26T18:39:54.057 回答
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);
        }
    }
于 2010-06-18T17:10:15.783 回答
0

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

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

于 2011-09-21T20:41:23.283 回答
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 );

        }

    }
于 2011-02-27T15:25:56.600 回答
0

我知道这是一个非常古老的问题,但它今天仍然存在。我的解决方法是将自动完成模式和源属性设置为“无”并手动更新 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;
    }
于 2018-04-23T14:00:45.527 回答
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;
}

脚步:

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

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

于 2018-11-29T12:58:05.393 回答
-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;
        }

}
于 2013-12-13T10:50:25.017 回答