How to SuggestAppend a ComboBox containing a string

倾然丶 夕夏残阳落幕 提交于 2019-11-28 12:44:38
BenD

I did some research and found the following question:

Override Winforms ComboBox Autocomplete Suggest Rule

In that question they reffer to another question:

C# AutoComplete

Let's quote the best answer from that question

The existing AutoComplete functionality only supports searching by prefix. There doesn't seem to be any decent way to override the behavior.

Some people have implemented their own autocomplete functions by overriding the OnTextChanged event. That's probably your best bet.

For example, you can add a ListBox just below the TextBox and set its default visibility to false. Then you can use the OnTextChanged event of the TextBox and the SelectedIndexChanged event of the ListBox to display and select items.

This seems to work pretty well as a rudimentary example:

public Form1()
{
    InitializeComponent();


    acsc = new AutoCompleteStringCollection();
    textBox1.AutoCompleteCustomSource = acsc;
    textBox1.AutoCompleteMode = AutoCompleteMode.None;
    textBox1.AutoCompleteSource = AutoCompleteSource.CustomSource;
}

private void button1_Click(object sender, EventArgs e)
{
    acsc.Add("[001] some kind of item");
    acsc.Add("[002] some other item");
    acsc.Add("[003] an orange");
    acsc.Add("[004] i like pickles");
}

void textBox1_TextChanged(object sender, System.EventArgs e)
{
    listBox1.Items.Clear();
    if (textBox1.Text.Length == 0)
    {
    hideResults();
    return;
    }

    foreach (String s in textBox1.AutoCompleteCustomSource)
    {
    if (s.Contains(textBox1.Text))
    {
        Console.WriteLine("Found text in: " + s);
        listBox1.Items.Add(s);
        listBox1.Visible = true;
    }
    }
}

void listBox1_SelectedIndexChanged(object sender, System.EventArgs e)
{
    textBox1.Text = listBox1.Items[listBox1.SelectedIndex].ToString();
    hideResults();
}

void listBox1_LostFocus(object sender, System.EventArgs e)
{
    hideResults();
}

void hideResults()
{
    listBox1.Visible = false;
}

There's a lot more you could do without too much effort: append text to the text box, capture additional keyboard commands, and so forth.

Improved the technique demonstrated by BenD in his answer so as to have the mechanism handle a bit more elegantly certain cornercases:

public sealed class CCComboboxAutocomplete : ComboBox
{
    public CCComboboxAutocomplete()
    {
        AutoCompleteMode = AutoCompleteMode.Suggest; //crucial otherwise exceptions occur when the user types in text which is not found in the autocompletion list
    }

    protected override void OnTextChanged(EventArgs e)
    {
        try
        {
            if (DesignMode || !string.IsNullOrEmpty(Text) || !Visible) return;

            ResetCompletionList();
        }
        finally
        {
            base.OnTextChanged(e);
        }
    }

    protected override void OnKeyPress(KeyPressEventArgs e)
    {
        try
        {
            if (DesignMode) return;
            if (e.KeyChar == '\r' || e.KeyChar == '\n')
            {
                e.Handled = true;
                if (SelectedIndex == -1 && Items.Count > 0 && Items[0].ToString().ToLowerInvariant().StartsWith(Text.ToLowerInvariant()))
                {
                    Text = Items[0].ToString();
                }
                DroppedDown = false;
                return; //0
            }

            BeginInvoke(new Action(ReevaluateCompletionList)); //1
        }
        finally
        {
            base.OnKeyPress(e);
        }
    }
    //0 Guardclose when detecting any enter keypresses to avoid a glitch which was selecting an item by means of down arrow key followed by enter to wipe out the text within
    //1 Its crucial that we use begininvoke because we need the changes to sink into the textfield  Omitting begininvoke would cause the searchterm to lag behind by one character  That is the last character that got typed in

    private void ResetCompletionList()
    {
        _previousSearchterm = null;
        try
        {
            SuspendLayout();

            var originalList = (object[])Tag;
            if (originalList == null)
            {
                Tag = originalList = Items.Cast<object>().ToArray();
            }

            if (Items.Count == originalList.Length) return;

            while (Items.Count > 0)
            {
                Items.RemoveAt(0);
            }

            Items.AddRange(originalList);
        }
        finally
        {
            ResumeLayout(performLayout: true);
        }
    }

    private string _previousSearchterm;
    private void ReevaluateCompletionList()
    {
        var currentSearchterm = Text.ToLowerInvariant();
        if (currentSearchterm == _previousSearchterm) return; //optimization

        _previousSearchterm = currentSearchterm;
        try
        {
            SuspendLayout();

            var originalList = (object[])Tag;
            if (originalList == null)
            {
                Tag = originalList = Items.Cast<object>().ToArray(); //0
            }

            var newList = (object[])null;
            if (string.IsNullOrEmpty(currentSearchterm))
            {
                if (Items.Count == originalList.Length) return;

                newList = originalList;
            }
            else
            {
                newList = originalList.Where(x => x.ToString().ToLowerInvariant().Contains(currentSearchterm)).ToArray();
            }

            try
            {
                while (Items.Count > 0) //1
                {
                    Items.RemoveAt(0);
                }
            }
            catch
            {
                try
                {
                    Items.Clear();
                }
                catch
                {
                }
            }


            Items.AddRange(newList.ToArray()); //2
        }
        finally
        {
            if (currentSearchterm.Length >= 2 && !DroppedDown)
            {
                DroppedDown = true; //3
                Cursor.Current = Cursors.Default; //4
                Text = currentSearchterm; //5
                Select(currentSearchterm.Length, 0);
            }

            ResumeLayout(performLayout: true);
        }
    }
    //0 backup original list
    //1 clear list by loop through it otherwise the cursor would move to the beginning of the textbox
    //2 reset list
    //3 if the current searchterm is empty we leave the dropdown list to whatever state it already had
    //4 workaround for the fact the cursor disappears due to droppeddown=true  This is a known bu.g plaguing combobox which microsoft denies to fix for years now
    //5 Another workaround for a glitch which causes all text to be selected when there is a matching entry which starts with the exact text being typed in
}

Sorry for another answer in C# but I have a more improved answer based on xDisruptor's code.

Using kinda behavior (decorator).

You don't have to subclass ComboBox and change all existing combos in the designed.

Be careful when using Datasource instead of Items collection, because it'll raise an exception.

Code:

public class AutoCompleteBehavior
{
    private readonly ComboBox comboBox;
    private string previousSearchterm;

    private object[] originalList;

    public AutoCompleteBehavior(ComboBox comboBox)
    {
        this.comboBox = comboBox;
        this.comboBox.AutoCompleteMode = AutoCompleteMode.Suggest; // crucial otherwise exceptions occur when the user types in text which is not found in the autocompletion list
        this.comboBox.TextChanged += this.OnTextChanged;
        this.comboBox.KeyPress += this.OnKeyPress;
        this.comboBox.SelectionChangeCommitted += this.OnSelectionChangeCommitted;
    }

    private void OnSelectionChangeCommitted(object sender, EventArgs e)
    {
        if (this.comboBox.SelectedItem == null)
        {
            return;
        }

        var sel = this.comboBox.SelectedItem;
        this.ResetCompletionList();
        this.comboBox.SelectedItem = sel;
    }

    private void OnTextChanged(object sender, EventArgs e)
    {
        if (!string.IsNullOrEmpty(this.comboBox.Text) || !this.comboBox.Visible || !this.comboBox.Enabled)
        {
            return;
        }

        this.ResetCompletionList();
    }

    private void OnKeyPress(object sender, KeyPressEventArgs e)
    {
        if (e.KeyChar == '\r' || e.KeyChar == '\n')
        {
            e.Handled = true;
            if (this.comboBox.SelectedIndex == -1 && this.comboBox.Items.Count > 0
                && this.comboBox.Items[0].ToString().ToLowerInvariant().StartsWith(this.comboBox.Text.ToLowerInvariant()))
            {
                this.comboBox.Text = this.comboBox.Items[0].ToString();
            }

            this.comboBox.DroppedDown = false;

            // Guardclause when detecting any enter keypresses to avoid a glitch which was selecting an item by means of down arrow key followed by enter to wipe out the text within
            return;
        }

        // Its crucial that we use begininvoke because we need the changes to sink into the textfield  Omitting begininvoke would cause the searchterm to lag behind by one character  That is the last character that got typed in
        this.comboBox.BeginInvoke(new Action(this.ReevaluateCompletionList));
    }

    private void ResetCompletionList()
    {
        this.previousSearchterm = null;
        try
        {
            this.comboBox.SuspendLayout();

            if (this.originalList == null)
            {
                this.originalList = this.comboBox.Items.Cast<object>().ToArray();
            }

            if (this.comboBox.Items.Count == this.originalList.Length)
            {
                return;
            }

            while (this.comboBox.Items.Count > 0)
            {
                this.comboBox.Items.RemoveAt(0);
            }

            this.comboBox.Items.AddRange(this.originalList);
        }
        finally
        {
            this.comboBox.ResumeLayout(true);
        }
    }

    private void ReevaluateCompletionList()
    {
        var currentSearchterm = this.comboBox.Text.ToLowerInvariant();
        if (currentSearchterm == this.previousSearchterm)
        {
            return;
        }

        this.previousSearchterm = currentSearchterm;
        try
        {
            this.comboBox.SuspendLayout();

            if (this.originalList == null)
            {
                this.originalList = this.comboBox.Items.Cast<object>().ToArray(); // backup original list
            }

            object[] newList;
            if (string.IsNullOrEmpty(currentSearchterm))
            {
                if (this.comboBox.Items.Count == this.originalList.Length)
                {
                    return;
                }

                newList = this.originalList;
            }
            else
            {
                newList = this.originalList.Where(x => x.ToString().ToLowerInvariant().Contains(currentSearchterm)).ToArray();
            }

            try
            {
                // clear list by loop through it otherwise the cursor would move to the beginning of the textbox
                while (this.comboBox.Items.Count > 0)
                {
                    this.comboBox.Items.RemoveAt(0);
                }
            }
            catch
            {
                try
                {
                    this.comboBox.Items.Clear();
                }
                catch (Exception ex)
                {
                    Debug.WriteLine(ex.Message);
                }
            }

            this.comboBox.Items.AddRange(newList.ToArray()); // reset list
        }
        finally
        {
            if (currentSearchterm.Length >= 1 && !this.comboBox.DroppedDown)
            {
                this.comboBox.DroppedDown = true; // if the current searchterm is empty we leave the dropdown list to whatever state it already had
                Cursor.Current = Cursors.Default; // workaround for the fact the cursor disappears due to droppeddown=true  This is a known bu.g plaguing combobox which microsoft denies to fix for years now
                this.comboBox.Text = currentSearchterm; // Another workaround for a glitch which causes all text to be selected when there is a matching entry which starts with the exact text being typed in
                this.comboBox.Select(currentSearchterm.Length, 0);
            }

            this.comboBox.ResumeLayout(true);
        }
    }
}

Usege:

new AutoCompleteBehavior(this.comboBoxItems);
this.comboBoxItems.Items.AddRange(new object[] { "John", "Tina", "Doctor", "Alaska" });

TIP: Can be further improved by making an extension to the ComboBox class like myCombo.ToAutoComplete()

A ComboBox,TextBox and I think a DropDownList has AutoComplete properties Look at http://msdn.microsoft.com/en-us/library/system.windows.forms.combobox.autocompletemode(v=vs.110).aspx

It explains which AutoCompleteMode you should use and how to set the AutoCompleteSource

You could try the following lines, it worked for me

 cbxName.AutoCompleteMode = AutoCompleteMode.SuggestAppend;
 cbxName.AutoCompleteSource = AutoCompleteSource.ListItems;
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!