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 the ListBox
below/above that.
- 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.
public static List<string> autoCompleteList = new List<string>();
private bool selectionComplete = false;
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.
private String getLatestString()
{
return textBox_next_invitees.Text.ToString().Substring
(textBox_next_invitees.Text.ToString().LastIndexOf(";")+1).Trim();
}
private void textBox_next_invitees_TextChanged(object sender, EventArgs e)
{
listBox1.Items.Clear();
if (textBox_next_invitees.Text.Length == 0)
{
hideAutoCompleteMenu();
return;
}
String compareText = getLatestString();
foreach (String s in autoCompleteList)
{
if (compareText == null ||
compareText.Equals("") || s.StartsWith(compareText.Trim()))
{
listBox1.Items.Add(s);
}
}
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;
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)
{
selectionComplete = true;
textBox_next_invitees.Focus();
listBox1_SelectedIndexChanged(sender, e);
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 the Listbox
- 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();
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;
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();
listBox1_SelectedIndexChanged(sender, e);
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)
{
listBox1.Visible = false;
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