Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / productivity / Office

Type-Ahead Suggestion Box using Listbox

4.00/5 (3 votes)
2 Mar 2015CPOL3 min read 12.5K  
Outlook email recipient type type-ahead feature using Listbox

Introduction

Sometime back, I needed to workout a solution to implement a type-ahead box like utility for a Winform application.

This screenshot illustrates what I was trying to achieve.

If you have worked in ASP.NET, you know this one is easy and can be bundled with a simple control like a textbox. But, winform does not have a very easy-to-use plugin for features like this.

Using the Code

What we are going to do here to achieve the above feature is implement a type-ahead functionality using ListBox.

In the below code example, the user can type initials of pre-defined email list inside a "RichTextBox" control and the backend code populates a ListBox with matching entries.

There are a couple of key tricks here to implement the solution:

  1. Find the exact location of the cursor within the textbox and position the ListBox below/above that.

  2. Handling the respective event from ListBox so that when the user clicks an entry within the ListBox, the selected entry is pushed to the textbox.

Let's get started.

C#
//
// First define the Master List which will hold all the email ids

       public static  List<string> autoCompleteList = new List<string>();
     
//This flag has a special purpose - time saving - will discuss in later part of the code
       private bool selectionComplete = false;

//Also, define the function which will toggle the visibility of the Listbox
     private void hideAutoCompleteMenu()
     {
       listBox1.Visible = false;
     }

In the above code snippet, we defined the autocomplete master list and introduced a function to toggle the visibility of the suggestionbox (our listbox).

Next is to create the textChanged event of the textbox; whenever the user types in something on the textbox, the suggestionbox will show up with matching suggestion at the proper position.

textBox_next_invitees is our richtextbox where user is typing the email addresses.

C#
//Return the current string from the rich textbox  
    private String getLatestString()
     {
       return textBox_next_invitees.Text.ToString().Substring
		(textBox_next_invitees.Text.ToString().LastIndexOf(";")+1).Trim();
     }

// textChanges event of the textBox

 private void textBox_next_invitees_TextChanged(object sender, EventArgs e)    
  {
                listBox1.Items.Clear();
         if (textBox_next_invitees.Text.Length == 0)
         {
           hideAutoCompleteMenu(); //Hide the Listbox if there is no text available in the textbox
           return;
         }

                 String compareText = getLatestString();
         foreach (String s in autoCompleteList)
         {
           if (compareText == null || 
           compareText.Equals("") || s.StartsWith(compareText.Trim()))
           {
                     listBox1.Items.Add(s);
                     }
                 }

//The following is the tricky part
//Get the current cursor position from the richtextbox
// Then increase the Y -index to position the listbox above the text 
// (decrease this value if you want the suggestion box below the text)

         if (listBox1.Items.Count > 0)
         {
           Point point = this.textBox_next_invitees.GetPositionFromCharIndex
			(textBox_next_invitees.SelectionStart);
           point.Y += (int)Math.Ceiling(this.textBox_next_invitees.Font.GetHeight()) + 2;
           point.X += 2;
           listBox1.Location = point;
//Make sure to bring the ListBox to front - otherwise it might go hiding behind the Rich text box
           this.listBox1.BringToFront();
           this.listBox1.Show();
         }
}

The next step is to allow the user to be able to click on a selected item on the listbox and the selected item should append at the end of the textbox.

C#
private void listBox1_MouseDoubleClick(object sender, MouseEventArgs e)
{
        //This boolean flag has a specific purpose - will discus shortly
  selectionComplete = true;
  textBox_next_invitees.Focus();


  listBox1_SelectedIndexChanged(sender, e);

  //Now set the cursor at the end of the line inside the textbox
  textBox_next_invitees.SelectionStart = textBox_next_invitees.Text.Length + 1;
  textBox_next_invitees.SelectionLength = 0;
}

Now, here is the selctedIndexChanged event of the ListBox. Once the user selects an item from the Listbox.

by double - clicking on the item, the following event will be called.

Few things we are doing here are listed below:

  1. Getting the selected item from the listbox
  2. Replacing the initial letters of the email address which the user typed inside the textbox with the selected email address from the Listbox
  3. Hiding the Listbox after the new email address is appended

Now, all these calculations need not be done every time the user traverses through items within the listbox; it should only be done once the user double clicks (or press- the enter key) on an item within the Listbox. This saves time. This is why we are using the selectionComplete boolean and setting it to true inside the

C#
listBox1_MouseDoubleClick

event.

C#
private void listBox1_SelectedIndexChanged(object sender, System.EventArgs e)
   if (selectionComplete)       
         {
              Rectangle rc = listBox1.GetItemRectangle(listBox1.SelectedIndex);
         LinearGradientBrush brush = new LinearGradientBrush(
           rc, Color.Transparent, Color.Red, LinearGradientMode.ForwardDiagonal);
         Graphics g = Graphics.FromHwnd(listBox1.Handle);

                      g.FillRectangle(brush, rc);
         if (listBox1.SelectedIndex >= 0)
         {
           int index = listBox1.SelectedIndex;
                    String newItem = listBox1.Items[index].ToString();

//Removing the last in-completely typed email address from the textbox - 
//this will be replaced by the selected item from the ListBox
//e.g. If the user typed 'sh' 
//and then selected shibasis.sengupta@gmail.com from the listbox,
// then remove 'sh' from the textbox and replace this
//with the selected complete email address from the listbox

           textBox_next_invitees.Text = textBox_next_invitees.Text.ToString().Remove
           	(textBox_next_invitees.Text.ToString().LastIndexOf(";") + 1);
           int start = textBox_next_invitees.Text.Length + 1;
           textBox_next_invitees.Text = textBox_next_invitees.Text + 
           " " + newItem + "; ";

                 this.textBox_next_invitees.SelectionStart = start;
           this.textBox_next_invitees.SelectionLength = listBox1.Items[index].ToString().Length;

//Highlight the newly entered email address inside the textbox
   textBox_next_invitees.SelectionFont = new Font
		(textBox_next_invitees.SelectionFont, FontStyle.Underline);
   textBox_next_invitees.SelectionBackColor = Color.FromArgb(0, 215, 228, 188);
    
   hideAutoCompleteMenu();
   selectionComplete = false;
   }
   }
   }

Now, the ListBox acts as a type ahead suggestion box as soon as the user types something on the textbox.

There is still some fine-tuning pending:

  1. How can the user use the enter key to select and item on the list
  2. As in Outlook, the Listbox should vanish if the user presses esc/ the left or right arrow key or even the backspace

For this to be achieved, we need to handle the KeyDown event of the ListBox.

C#
private void listBox1_KeyDown(object sender, KeyEventArgs e)
{
    if (e.KeyCode == Keys.Enter)
    {
        selectionComplete = true;
        textBox_next_invitees.Focus();

        //Invoke the selectedIndexChanged event of the listbox in case the user presses the enter key
        listBox1_SelectedIndexChanged(sender, e);

        //Now set the cursor at the end of the line inside the textbox
        textBox_next_invitees.SelectionStart = textBox_next_invitees.Text.Length + 1;
        textBox_next_invitees.SelectionLength = 0;
    }
    if (e.KeyCode == Keys.Escape || e.KeyCode == Keys.Right || 
			e.KeyCode == Keys.Left ||e.KeyCode==Keys.Back)
    {
        //hide the listbox in case the user presses any of the above keys
        listBox1.Visible = false;

        //focus on the textbox - so the user can see the cursor on the textbox
        textBox_next_invitees.Focus();
    }
}

Now, the final touch. Like in Outlook, we should allow the user to be able to use the up/down arrow key to traverse through the suggestion list, while typing inside the textbox. For this, we need to handle the KeyDown event of the textbox.

C#
private void textBox_next_invitees_KeyDown(object sender, KeyEventArgs e)
{
    if (listBox1.Visible && (e.KeyCode == Keys.Up || e.KeyCode == Keys.Down))
    {
        listBox1.Select();
        listBox1.SetSelected(0, true);
    }
}

Points of Interest

The feature described here can be bundled as a cutom control and distributed.

History

  • 2nd March, 2015: Updated
  • 3rd March, 2015: Last updated

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)