C# winforms combobox dynamic autocomplete

142,423

Solution 1

Here is my final solution. It works fine with a large amount of data. I use Timer to make sure the user want find current value. It looks like complex but it doesn't. Thanks to Max Lambertini for the idea.

        private bool _canUpdate = true; 

        private bool _needUpdate = false;       

        //If text has been changed then start timer
        //If the user doesn't change text while the timer runs then start search
        private void combobox1_TextChanged(object sender, EventArgs e)
        {
            if (_needUpdate)
            {
                if (_canUpdate)
                {
                    _canUpdate = false;
                    UpdateData();                   
                }
                else
                {
                    RestartTimer();
                }
            }
        }

        private void UpdateData()
        {
            if (combobox1.Text.Length > 1)
            {
                List<string> searchData = Search.GetData(combobox1.Text);
                HandleTextChanged(searchData);
            }
        }       

        //If an item was selected don't start new search
        private void combobox1_SelectedIndexChanged(object sender, EventArgs e)
        {
            _needUpdate = false;
        }

        //Update data only when the user (not program) change something
        private void combobox1_TextUpdate(object sender, EventArgs e)
        {
            _needUpdate = true;
        }

        //While timer is running don't start search
        //timer1.Interval = 1500;
        private void RestartTimer()
        {
            timer1.Stop();
            _canUpdate = false;
            timer1.Start();
        }

        //Update data when timer stops
        private void timer1_Tick(object sender, EventArgs e)
        {
            _canUpdate = true;
            timer1.Stop();
            UpdateData();
        }

        //Update combobox with new data
        private void HandleTextChanged(List<string> dataSource)
        {
            var text = combobox1.Text;

            if (dataSource.Count() > 0)
            {
                combobox1.DataSource = dataSource;  

                var sText = combobox1.Items[0].ToString();
                combobox1.SelectionStart = text.Length;
                combobox1.SelectionLength = sText.Length - text.Length;
                combobox1.DroppedDown = true;


                return;
            }
            else
            {
                combobox1.DroppedDown = false;
                combobox1.SelectionStart = text.Length;
            }
        }

This solution isn't very cool. So if someone has another solution please share it with me.

Solution 2

I also came across these kinds of requirements recently. I set the below properties without writing the code and it works. See if this helps you.

AutoCompleteMode: SuggestAppend // AutoCompleteSource: ListItems // DropDownStyle: DropDownList

Solution 3

I wrote something like this ....

private void frmMain_Load(object sender, EventArgs e)
{
    cboFromCurrency.Items.Clear();
    cboComboBox1.AutoCompleteMode = AutoCompleteMode.Suggest;
    cboComboBox1.AutoCompleteSource = AutoCompleteSource.ListItems;
    // Load data in comboBox => cboComboBox1.DataSource = .....
    // Other things
}

private void cboComboBox1_KeyPress(object sender, KeyPressEventArgs e)
{
    cboComboBox1.DroppedDown = false;
}

That's all (Y)

Solution 4

Yes, you surely can... but it needs some work to make it work seamlessly. This is some code I came up with. Bear in mind that it does not use combobox's auto-complete features, and it might be quite slow if you use it to sift thru a lot of items...

string[] data = new string[] {
    "Absecon","Abstracta","Abundantia","Academia","Acadiau","Acamas",
    "Ackerman","Ackley","Ackworth","Acomita","Aconcagua","Acton","Acushnet",
    "Acworth","Ada","Ada","Adair","Adairs","Adair","Adak","Adalberta","Adamkrafft",
    "Adams"

};
public Form1()
{
    InitializeComponent();
}

private void comboBox1_TextChanged(object sender, EventArgs e)
{
    HandleTextChanged();
}

private void HandleTextChanged()
{
    var txt = comboBox1.Text;
    var list = from d in data
               where d.ToUpper().StartsWith(comboBox1.Text.ToUpper())
               select d;
    if (list.Count() > 0)
    {
        comboBox1.DataSource = list.ToList();
        //comboBox1.SelectedIndex = 0;
        var sText = comboBox1.Items[0].ToString();
        comboBox1.SelectionStart = txt.Length;
        comboBox1.SelectionLength = sText.Length - txt.Length;
        comboBox1.DroppedDown = true;
        return;
    }
    else
    {
        comboBox1.DroppedDown = false;
        comboBox1.SelectionStart = txt.Length;
    }
}

private void comboBox1_KeyUp(object sender, KeyEventArgs e)
{
    if (e.KeyCode == Keys.Back)
    {
        int sStart = comboBox1.SelectionStart;
        if (sStart > 0)
        {
            sStart--;
            if (sStart == 0)
            {
                comboBox1.Text = "";
            }
            else
            {
                comboBox1.Text = comboBox1.Text.Substring(0, sStart);
            }
        }
        e.Handled = true;
    }
}

Solution 5

I've found Max Lambertini's answer very helpful, but have modified his HandleTextChanged method as such:

    //I like min length set to 3, to not give too many options 
    //after the first character or two the user types
    public Int32 AutoCompleteMinLength {get; set;}

    private void HandleTextChanged() {
        var txt = comboBox.Text;
        if (txt.Length < AutoCompleteMinLength)
            return;

        //The GetMatches method can be whatever you need to filter 
        //table rows or some other data source based on the typed text.
        var matches = GetMatches(comboBox.Text.ToUpper());

        if (matches.Count() > 0) {
            //The inside of this if block has been changed to allow
            //users to continue typing after the auto-complete results
            //are found.
            comboBox.Items.Clear();
            comboBox.Items.AddRange(matches);
            comboBox.DroppedDown = true;
            Cursor.Current = Cursors.Default;
            comboBox.Select(txt.Length, 0);
            return;
        }
        else {
            comboBox.DroppedDown = false;
            comboBox.SelectionStart = txt.Length;
        }
    }
Share:
142,423
algreat
Author by

algreat

Updated on April 13, 2021

Comments

  • algreat
    algreat about 3 years

    My problem is similar to this one: How can I dynamically change auto complete entries in a C# combobox or textbox? But I still don't find solution.

    The problem briefly:

    I have an ComboBox and a large number of records to show in it. When user starts typing I want to load records that starts with input text and offer the user for autocomplete. As described in the topic above I can't load them on сomboBox_TextChanged because I always overwrite the previous results and never see them.

    Can I implement this using only ComboBox? (not TextBox or ListBox)

    I use this settings:

    сomboBox.AutoCompleteMode = AutoCompleteMode.SuggestAppend;
    сomboBox.AutoCompleteSource = AutoCompleteSource.CustomSource;
    
  • algreat
    algreat almost 12 years
    This is fine solution. It works better than default autocomplete. But it isn't still enough for large amount of data. So I'll try to improve your solution.
  • Max Lambertini
    Max Lambertini almost 12 years
    You could trigger autocomplete by starting queries on data only when, say, text length is more than three, four characters...
  • Andy
    Andy about 10 years
    If - like me - you find that using "combobox1.DroppedDown = true" is causing the mouse cursor to vanish add "Cursor.Current = Cursors.Default" after it.
  • Nathan Tuggy
    Nathan Tuggy about 9 years
    Generally, answers are much more helpful if they include an explanation of what the code is intended to do, and why that solves the problem without introducing others. (This post was flagged by at least one user, presumably because they thought an answer without explanation should be deleted.)
  • Jose Rodriguez
    Jose Rodriguez almost 8 years
    Use timeouts I think is a good choice, when the user stops typing after a predefined time, then begins to perform the query.
  • YıldızGezen
    YıldızGezen over 7 years
    The name 'Search' does not exist in the current context . What am i missing here?
  • Taja_100
    Taja_100 about 7 years
    Search is User defined function to get the datasource
  • Frank Monroe
    Frank Monroe about 7 years
    @Max - be careful as I was burned with this approach: some of the last names in the database were "Li" and I used 3 characters! :)
  • Der Wolf
    Der Wolf almost 6 years
    I liked this solution. One change I made is that I went with DropDownStyle=DropDown so that the user can start typing free form and it will then autocomplete from the list.
  • bh_earth0
    bh_earth0 about 3 years
    What you do with timer is "throttle debounce ", right? . you can use "debounce dispatcher" from rick strahl website , to get rid off all timer logic/complexity.