4

How do I change the autocomplete on a TextBox? I want that when I type a string, the box suggest items containing that string instead of starting with.

My code is:

class MyClass
{
    private AutoCompleteStringCollection autoCompleteList = new AutoCompleteStringCollection();

    public MyClass()
    {
        InitializeComponent();

        autoCompleteList.AddRange(ListNames.Select(x=>x.Name).ToArray());
        textBoxName.AutoCompleteCustomSource = autoCompleteList;
        textBoxName.AutoCompleteSource = AutoCompleteSource.CustomSource;
        textBoxName.AutoCompleteMode = AutoCompleteMode.Suggest;
        textBoxName.KeyDown += TextBoxtextName_KeyDown;
    }   

    private void TextBoxClient_KeyDown(object sender, KeyEventArgs e)
    {
        if (e.KeyData == Keys.Enter)
        {
            this.Name = (sender as TextBox).Text;
        }
    }     
}

What I want:

desired autocomplete

EpicKip
  • 4,015
  • 1
  • 20
  • 37
A.Pissicat
  • 3,023
  • 4
  • 38
  • 93
  • You will probably have to do it manually. Check the first answer here: http://stackoverflow.com/questions/7985620/autocomplete-contains-instead-of-starting-with-in-winform-textbox – Rufus L Apr 06 '17 at 12:24
  • @RufusL This answer suggest to use a combo box, but we can't write in it. I'll check if a custom control combo+textbox can help me. – A.Pissicat Apr 06 '17 at 12:33
  • @A.Pissicat Added the solution to your problem with a custom textbox – EpicKip Apr 06 '17 at 12:47

1 Answers1

8

Looking at your code you have everything you need but 1 line of code. That line is:

This will only work if the start of a string is entered

//Suggestion only
textBoxName.AutoCompleteMode = AutoCompleteMode.Suggest;
//Suggest and autocomplete
textBoxName.AutoCompleteMode = AutoCompleteMode.SuggestAppend;

This will work as a contains method but works with a custom control

You can also make a custom textbox control that fits your needs.
Custom textbox class:

public class AutoCompleteTextBox : TextBox
{
    private ListBox _listBox;
    private bool _isAdded;
    private String[] _values;
    private String _formerValue = String.Empty;

    public AutoCompleteTextBox()
    {
        InitializeComponent();
        ResetListBox();
    }

    private void InitializeComponent()
    {
        _listBox = new ListBox();
        this.KeyDown += this_KeyDown;
        this.KeyUp += this_KeyUp;
    }

    private void ShowListBox()
    {
        if (!_isAdded)
        {
            Parent.Controls.Add(_listBox);
            _listBox.Left = Left;
            _listBox.Top = Top + Height;
            _isAdded = true;
        }
        _listBox.Visible = true;
        _listBox.BringToFront();
    }

    private void ResetListBox()
    {
        _listBox.Visible = false;
    }

    private void this_KeyUp(object sender, KeyEventArgs e)
    {
        UpdateListBox();
    }

    private void this_KeyDown(object sender, KeyEventArgs e)
    {
        switch (e.KeyCode)
        {
            case Keys.Enter:
            case Keys.Tab:
            {
                if (_listBox.Visible)
                {
                    Text = _listBox.SelectedItem.ToString();
                    ResetListBox();
                    _formerValue = Text;
                    this.Select(this.Text.Length, 0);
                    e.Handled = true;
                }
                break;
            }
            case Keys.Down:
            {
                if ((_listBox.Visible) && (_listBox.SelectedIndex < _listBox.Items.Count - 1))
                    _listBox.SelectedIndex++;
                    e.Handled = true;
                    break;
                }
                case Keys.Up:
                {
                    if ((_listBox.Visible) && (_listBox.SelectedIndex > 0))
                        _listBox.SelectedIndex--;
                    e.Handled = true;
                    break;
                }


            }
        }

        protected override bool IsInputKey(Keys keyData)
        {
            switch (keyData)
            {
                case Keys.Tab:
                    if (_listBox.Visible)
                        return true;
                    else
                        return false;
                default:
                    return base.IsInputKey(keyData);
            }
        }

        private void UpdateListBox()
        {
            if (Text == _formerValue)
                return;

            _formerValue = this.Text;
            string word = this.Text;

            if (_values != null && word.Length > 0)
            {
                string[] matches = Array.FindAll(_values,
                    x => (x.ToLower().Contains(word.ToLower())));
                if (matches.Length > 0)
                {
                    ShowListBox();
                    _listBox.BeginUpdate();
                    _listBox.Items.Clear();
                    Array.ForEach(matches, x => _listBox.Items.Add(x));
                    _listBox.SelectedIndex = 0;
                    _listBox.Height = 0;
                    _listBox.Width = 0;
                    Focus();
                    using (Graphics graphics = _listBox.CreateGraphics())
                    {
                        for (int i = 0; i < _listBox.Items.Count; i++)
                        {
                            if (i < 20)
                            _listBox.Height += _listBox.GetItemHeight(i);
                        // it item width is larger than the current one
                        // set it to the new max item width
                        // GetItemRectangle does not work for me
                        // we add a little extra space by using '_'
                        int itemWidth = (int)graphics.MeasureString(((string)_listBox.Items[i]) + "_", _listBox.Font).Width;
                        _listBox.Width = (_listBox.Width < itemWidth) ? itemWidth : this.Width; ;
                    }
                }
                _listBox.EndUpdate();
            }
            else
            {
                ResetListBox();
            }
        }
        else
        {
            ResetListBox();
        }
    }

    public String[] Values
    {
        get
        {
            return _values;
        }
        set
        {
            _values = value;
        }
    }

    public List<String> SelectedValues
    {
        get
        {
            String[] result = Text.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
            return new List<String>(result);
        }
    }
}

Usage:

string[] nameArray = { "name1", "name2", "name3", "bla name" };
AutoCompleteTextBox tb = new AutoCompleteTextBox();
tb.Values = nameArray;
tb.Location = new Point(10,10);
tb.Size = new Size(25,75);
this.Controls.Add( tb );

I got the code for the custom control from: SO Question - Autocomplete contains

Community
  • 1
  • 1
EpicKip
  • 4,015
  • 1
  • 20
  • 37
  • [This question (Combobox AutoCompleteMode Suggest contain)](http://stackoverflow.com/questions/19020313/combobox-autocompletemode-suggest-contain) suggests that may not be enough – stuartd Apr 06 '17 at 12:07
  • @stuartd I just noted that and added a note to the answer. I'll try and find the full answer now :) – EpicKip Apr 06 '17 at 12:08
  • As I said, I need to search with a contain method. Maybe if I create a custom textbox and overriding the autocomplete method but I don't know how it works. I have set the autocompletemode in designer, I forgot to copy it in my question. – A.Pissicat Apr 06 '17 at 12:24
  • @A.Pissicat As I said, I noted that after posting my answer, looking for a solution – EpicKip Apr 06 '17 at 12:25
  • @J.Tuc I edited my question after EpicKip. Now I'm checking his new answer. – A.Pissicat Apr 06 '17 at 12:52
  • @EpicKip this solution is almost that I want but I have 2 bugs. The listBox doesn't appear on the bot of my Textbox. The bottom of the listbox is hide by other controls – A.Pissicat Apr 06 '17 at 13:01
  • @A.Pissicat hmm the listbox does appear directly under the textbox for me, ill try to fix it. Ill get back to you – EpicKip Apr 06 '17 at 13:05
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/140078/discussion-between-epickip-and-a-pissicat). – EpicKip Apr 06 '17 at 13:09
  • @EpicKip I'm seeing the same issue, if the `AutoCompleteTextBox` is inside a `GroupBox` the drop down auto complete list is cut off by the group box. Is there a way to get it to show beyond the boundaries of the group box? – rboy Apr 06 '22 at 16:10
  • Okay I solved it following the controls all the way to the Parent without a parent and adding the listbox there while adjusting the listbox top/left (add it) at each step along the way (initialize the listbox top with the current control height before the while loop and add top/left each level in a while loop). Now the listbox is added to the root parent control and doesn't get cut off – rboy Apr 07 '22 at 01:06
  • I tried to use this example. Mostly it is working, thank you @EpicKip, but I noticed that the mouse is not working with the list-box. The list-box does not receive mouse events. If I click the mouse above the list, the list will close... Anyone encounter this? – PazO Aug 13 '22 at 17:14
  • This looks good but there is no "autocomplete" it only shows you the results, but you can not click or autocomplete anything sadly. so this is not really useful if it's just a dropdown without interaction – slow Jul 24 '23 at 11:10
  • @slow This is a really old answer I gave so I'm not sure about the specifics anymore but if you add handlers to to listbox(items) you should be able to achieve what you want (add enter/click handlers that inserts the selected item). – EpicKip Jul 24 '23 at 11:12