Tuesday, February 8, 2011

Control for tags with auto-completion in Winforms?

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?

  • 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;
        }
    }
    

0 comments:

Post a Comment