Type-Ahead Suggestion Box using Listbox





4.00/5 (3 votes)
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:
- Find the exact location of the cursor within the
textbox
and position theListBox
below/above that. - Handling the respective event from
ListBox
so that when the user clicks an entry within theListBox
, the selected entry is pushed to thetextbox
.
Let's get started.
//
// 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.
//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
.
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:
- Getting the selected item from the
listbox
- Replacing the initial letters of the email address which the user typed inside the
textbox
with the selected email address from theListbox
- 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
listBox1_MouseDoubleClick
event.
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:
- How can the user use the enter key to select and item on the list
- 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
.
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
.
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