views:

298

answers:

1

I am seeking a WinForm control that would provide an autocomplete behavior for multiple space-separated - exactly ala del.icio.us (or stackoverflow.com for that matter).

Does anyone knows how to do that within a .NET 2.0 WinForm application?

+2  A: 

ComboBox can autocomplete, but only one word at a time.

If you want to have each word separately autocompleted, you have to write your own.

I already did, hope it's not too long. It's not 100% exactly what you want, this was used for autocompleting in email client when typing in email adress.

/// <summary>
/// Extended TextBox with smart auto-completion
/// </summary>
public class TextBoxAC: TextBox
{
    private List<string> completions = new List<string>();
    private List<string> completionsLow = new List<string>();
    private bool autocompleting = false;
    private bool acDisabled = true;

    private List<string> possibleCompletions = new List<string>();
    private int currentCompletion = 0;

    /// <summary>
    /// Default constructor
    /// </summary>
    public TextBoxAC()
    {
        this.TextChanged += new EventHandler(TextBoxAC_TextChanged);
        this.KeyPress += new KeyPressEventHandler(TextBoxAC_KeyPress);
        this.KeyDown += new KeyEventHandler(TextBoxAC_KeyDown);

        this.TabStop = true;
    }

    /// <summary>
    /// Sets autocompletion data, list of possible strings
    /// </summary>
    /// <param name="words">Completion words</param>
    /// <param name="wordsLow">Completion words in lowerCase</param>
    public void SetAutoCompletion(List<string> words, List<string> wordsLow)
    {
        if (words == null || words.Count < 1) { return; }

        this.completions = words;
        this.completionsLow = wordsLow;

        this.TabStop = false;
    }

    private void TextBoxAC_TextChanged(object sender, EventArgs e)
    {
        if (this.autocompleting || this.acDisabled) { return; }


        string text = this.Text;
        if (text.Length != this.SelectionStart) { return; }

        int pos = this.SelectionStart;
        string userPrefix = text.Substring(0, pos);
        int commaPos = userPrefix.LastIndexOf(",");

        if (commaPos == -1)
        {
            userPrefix = userPrefix.ToLower();
            this.possibleCompletions.Clear();
            int n = 0;
            foreach (string s in this.completionsLow)
            {
                if (s.StartsWith(userPrefix))
                {
                    this.possibleCompletions.Add(this.completions[n]);
                }
                n++;
            }
            if (this.possibleCompletions.Count < 1) { return; }

            this.autocompleting = true;
            this.Text = this.possibleCompletions[0];
            this.autocompleting = false;
            this.SelectionStart = pos;
            this.SelectionLength = this.Text.Length - pos;
        }
        else
        {
            string curUs = userPrefix.Substring(commaPos + 1);

            if (curUs.Trim().Length < 1) { return; }

            string trimmed;
            curUs = this.trimOut(curUs, out trimmed);
            curUs = curUs.ToLower();

            string oldUs = userPrefix.Substring(0, commaPos + 1);

            this.possibleCompletions.Clear();
            int n = 0;
            foreach (string s in this.completionsLow)
            {
                if (s.StartsWith(curUs))
                {
                    this.possibleCompletions.Add(this.completions[n]);
                }
                n++;
            }
            if (this.possibleCompletions.Count < 1) { return; }

            this.autocompleting = true;
            this.Text = oldUs + trimmed + this.possibleCompletions[0];
            this.autocompleting = false;
            this.SelectionStart = pos;
            this.SelectionLength = this.Text.Length - pos + trimmed.Length;
        }
        this.currentCompletion = 0;
    }

    private void TextBoxAC_KeyDown(object sender, KeyEventArgs e)
    {
        if (e.KeyCode == Keys.Back || e.KeyCode == Keys.Delete)
        {
            this.acDisabled = true;
        }

        if (e.KeyCode == Keys.Up || e.KeyCode == Keys.Down)
        {
            if ((this.acDisabled) || (this.possibleCompletions.Count < 1))
            {
                return;
            }

            e.Handled = true;
            if (this.possibleCompletions.Count < 2) { return; }

            switch (e.KeyCode)
            {
                case Keys.Up:
                    this.currentCompletion--;
                    if (this.currentCompletion < 0)
                    {
                        this.currentCompletion = this.possibleCompletions.Count - 1;
                    }
                    break;
                case Keys.Down:
                    this.currentCompletion++;

                    if (this.currentCompletion >= this.possibleCompletions.Count)
                    {
                        this.currentCompletion = 0;
                    }
                    break;
            }

            int pos = this.SelectionStart;
            string userPrefix = this.Text.Substring(0, pos);
            int commaPos = userPrefix.LastIndexOf(",");

            if (commaPos == -1)
            {
                pos--;
                userPrefix = this.Text.Substring(0, pos);

                this.autocompleting = true;
                this.Text = userPrefix + this.possibleCompletions[this.currentCompletion].Substring(userPrefix.Length);
                this.autocompleting = false;
                this.SelectionStart = pos + 1;
                this.SelectionLength = this.Text.Length - pos;
            }
            else
            {
                string curUs = userPrefix.Substring(commaPos + 1);

                if (curUs.Trim().Length < 1) { return; }

                string trimmed;
                curUs = this.trimOut(curUs, out trimmed);
                curUs = curUs.ToLower();

                string oldUs = userPrefix.Substring(0, commaPos + 1);

                this.autocompleting = true;
                this.Text = oldUs + trimmed + this.possibleCompletions[this.currentCompletion];
                this.autocompleting = false;
                this.SelectionStart = pos;
                this.SelectionLength = this.Text.Length - pos + trimmed.Length;
            }
        }
    }

    private void TextBoxAC_KeyPress(object sender, KeyPressEventArgs e)
    {
        if (!Char.IsControl(e.KeyChar)) { this.acDisabled = false; }
    }

    private string trimOut(string toTrim, out string trim)
    {
        string ret = toTrim.TrimStart();

        int pos = toTrim.IndexOf(ret);
        trim = toTrim.Substring(0, pos);

        return ret;
    }
}
Jakub Kotrla