Welcome to my tutorial on Creating You Own AutoComplete TextBox in C#. This tutorial assumes you have a good knowledge of making your own User Controls, and an understanding of overriding base events in the control you're inheriting from. In this tutorial we will be creating a TextBox that has an auto complete ability, retrieving its values from a text file.
In this tutorial we will make use of the following:
The latter 2 we will actually be overriding the base functionality to accomplish our specific tasks. Now we dive into the code. When creating this control the first thing we need to do is bring in references to the appropriate Namespaces, we do this with the
using statement, so add these Namespaces to the top of your user control class:
CODE
using System;
using System.Diagnostics;
using System.Windows.Forms;
using System.ComponentModel;
using System.IO;
using System.Collections;
Those are the Namespaces we will be making use of in creating our control. Second we will want a Namespace for our control, mine, since I am PsychoCoder, is PC, and it looks like this
CODE
namespace PC
{
//we will add our control in here
}
Since we're inheriting from an existing control library, we need to make sure we add that in the signature of our class
CODE
public class AutoComplete : TextBox
{
/all of our code will go in here
}
Now, in this class I have variables that are referenced throughout the class, so I made them Global variables. The debate rages on over the use of Global variables and I agree with parts of both arguments so Ill say this, it's ok in my mind to use global variables, just use them sparingly and cautiously. Here are the global variables we need for this control:
CODE
#region "Variables"
//List that holds the values to be loaded
//by the textbox
private ArrayList _reloadValues = new ArrayList();
//Flag used to tell the keypress
//handler if it should autocomplete
private bool _updateValues = false;
//The filename of the file that the
//list is read from and saved to.
private string _file = "";
//Set to true when the _reloadValues is loaded.
private bool _init = false;
#endregion
NOTE: I always group my code in regions (
#region RegionName) as this makes it easier for me to find things and sections as I'm looking through my code.
2 of those global variables are for the only 2 properties of this control,
Values and
ValuesFile. These properties reference the actual string array of the values for the AutoComplete functionality and the file name the values are stored in:
CODE
#region "Properties"
/// <summary>
/// Sets the list of choices directly
/// </summary>
[Description("Sets the list of values")]
public string[] Values
{
get {return (string[])_reloadValues.ToArray(typeof(string));}
set {_reloadValues = new ArrayList(value);}
}
/// <summary>
/// The filename of the file to get the values from
/// </summary>
[Description("The filename of the file to load from")]
public string ValuesFile
{
get{return _file;}
set{ _file = value;}
}
#endregion
NOTE: Notice on the properties I have added a
Description, this is always a good idea when creating a user control, this gives a nice description, in code and in the IDE, what that property is to be used for.
Next thing we need for our class are
Constructors Constructors are very important in a class, this is what allows you to instantiate them in your form code, such as
AutoComplete auto = new AutoComplete();. Without constructors we couldn't instantiate our classes. We have 2 constructors, 1 that sets the file name to an empty string, and one that instantiates it to an actual file:
CODE
#region "Constructors"
/// <summary>
/// Constructor that initializes
/// filename to ""
/// </summary>
/// <remarks></remarks>
public AutoComplete()
{
_file = string.Empty;
}
/// <summary>
/// Constructor that sets the
/// file to an actual file
/// </summary>
/// <param name="file"></param>
public AutoComplete(string file)
{
_file = file;
}
#endregion
NOTE: Always use your XML Comment blocks to descript what taht method does, and
always provide good comments in your code.
Now that we have the properties and constructors defined, we can get into the
meat of the control, the methods that do the actual work. First we will look at a short method, that is used to call another method. The
RefreshTextBox method calls the
RefreshValues method, which is used to refresh or reload the values file the control is reading from:
CODE
/// <summary>
/// Causes the control to reload the values file.
/// </summary>
/// <remarks></remarks>
[Description("Causes the control to realod the values.")]
public void RefreshTextBox()
{
RefreshValues();
}
As you can see, here we call the
RefreshValues method so now we'll talke about that method. This method first checks to make sure the file name isnt empty, that a file has been passed, if it doesnt then it creates a file based on the control name with ".txt" appended to it. It then opens that file and repopulates our
ArrayList that is holding the values for our control:
CODE
/// <summary>
/// Refreshes the values already saved in the text file,
/// if no file exists then create one
/// </summary>
/// <remarks></remarks>
private void RefreshValues()
{
try
{
//check to see if a file name was provides, if
//one wasnt provided then create a new file with
//a name of the Control.txt
if (_file == "")
_file = this.Name + ".txt";
//re instantiate our array list object
_reloadValues = new ArrayList();
//open our file
FileStream stream = new FileStream(_file, FileMode.OpenOrCreate, FileAccess.ReadWrite);
//read the file
StreamReader reader = new StreamReader(stream);
//loop through each line in the file adding the value
//to our ArrayList object
while (reader.Peek() != -1)
_reloadValues.Add(reader.ReadLine());
//close out reader and stream objects
reader.Close();
stream.Close();
_init = true;
}
catch (Exception ee)
{
throw new Exception("Could not open the value file for the control: " + this.Name, ee);
}
}
So, you say, we're creating an AutoComplete TextBox which reads its values from a text file, so how do the values get into the file. Well I'm glad you asked. We have a method named
SaveFeedURL, I used this name as I created this file for the RSS Reader I created (located in another tutorial here on </dream.in.code>, you can change it if you like.
In this method we create our stream and writer objects, then we check to ensure a value was passed to this method, since we don't want to save an empty string to our file. From there we check to see if the value already exists in our file, as we don't want to save duplicate values. If all those pass, then we open the file, write to the file, then close it back up. If the file provided doesn't exist then we need to create one to hold our values.
Once the file is created we use the
ArrayList.GetEnumerator to return an enumeration we can use to iterate through the array and add each value to our new file. We then close the file, reallocating those resources.
CODE
public void SaveFeedURL(string url)
{
//create our objects
string str;
FileStream stream;
StreamWriter writer;
//check to make sure a value was passed
if (url.Trim() == "") return; //Don't save empty strings
bool isFound = IsPresent();
//check to see if the file already exists
//if it does we want to add to it, not
//create a new one
if (File.Exists(_file))
{
//check to make sure the value passed
//doesnt already exist in our file
if (isFound) return;
try
{
//add the value to our ArrayList
_reloadValues.Add(url);
//open our file
stream = new FileStream(_file, FileMode.Append);
writer = new StreamWriter(stream);
//write the value
writer.WriteLine(url);
//close everthing up
writer.Close();
stream.Close();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
throw new Exception("Could not save the value: " + url + " to the list", ex);
}
}
//the file was not found, so create it.
else
{
if (!isFound)
_reloadValues.Add(url);
try
{
//create an enumerator to iterate
//through our ArrayList with
IEnumerator enumerate = _reloadValues.GetEnumerator();
//create our file
stream = new FileStream(_file, FileMode.Create);
//generate a StreamWrite to write to
//our file with
writer = new StreamWriter(stream);
//now loop through the ArrayList adding
//each of the values to our file
while (enumerate.MoveNext())
{
str = (string)enumerate.Current;
writer.WriteLine(str);
//close everything up
writer.Close();
stream.Close();
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
throw new Exception("Could not save the value: " + url + " to the list", ex);
}
}
}
NOTE: Always release resources when you are finished with that particular object.
You will notice that in this method we make a reference to a method named
IsPresent, this is the method that takes the value being passed to the save method and makes sure it doesn't already exist in our file. Once again we use the
ArrayList.GetEnumerator Method to iterate through our file contents to see if this value exists, if it does we return True, if it doesn't we return False:
CODE
/// <summary>
/// Searches the file with the value
/// being passed for save
/// </summary>
/// <returns>True/False</returns>
private bool IsPresent()
{
//create an enumerator to iterate
//through our ArrayList
IEnumerator enumerate = _reloadValues.GetEnumerator();
string str = this.Text.ToUpper();
//loop through all the values looking
//for the value being daved. If it exists
//return true, e;se return false
while(enumerate.MoveNext())
{
string text = (string)enumerate.Current;
if(text.ToUpper().IndexOf(str) == 0)
return true;
}
return false;
}
That is the end of our methods that we create, the next 3 are overriding base methods in the TextBox Class. The first one we'll look at is the
OnTextChanged Event. This is the most complicated of our methods. We have to first make sure our ArrayList contains items, then we have to check each character typed into the textbox to see if a value in our file resembles whats currently in the textbox, if it does then we highlight the remaining text in the textbox as the user types, letting them know they possible have the value they're looking for:
CODE
/// <summary>
/// Overrides the TextChanged event. We will use this
/// to check and see if what the user is typing resembles anything
/// in our file
/// </summary>
/// <param name="e">An System.EventArgs that contains the event data.</param>
protected override void OnTextChanged(EventArgs e)
{
if(_reloadValues.Count == 0 && !_init) //Load the values from the file.
{
RefreshValues();
}
string tmp = this.Text.ToUpper();
if((tmp == "") || _updateValues)
{
base.OnTextChanged (e);
}
else
{
//create our enumerator to iterate through our ArrayList
IEnumerator enumerate = _reloadValues.GetEnumerator();
//set out bool value to false
bool isFound = false;
//loop while theres a value is our ArrayList
//and our boolean value is false
while((enumerate.MoveNext()) && (!isFound))
{
//set our string variable to the value of
//the current value in the enumerator
string str = (string)enumerate.Current;
//check to see if it holds one of our values
if(str.ToUpper().IndexOf(tmp) == 0)
{
//update our values
_updateValues = true;
//set the value from our file
//to the value in the textbox
this.Text = str;
//re-highlight the remaining text
//as the user types
this.SelectionStart = tmp.Length;
this.SelectionLength = str.Length - tmp.Length;
//set our boolean value to true
isFound = true;
_updateValues = false;
}
}
base.OnTextChanged (e);
}
}
The next 2 are relatively simple, we check to see if the user pressed one of the
normal keys, a letter or a number (no special characters), then act accordingly. Since they're both but 3 lines apiece, Ill show them both at once:
CODE
/// <summary>
/// Overrides KeyDown event.
/// </summary>
/// <param name="e">A System.Windows.Forms.KeyEventArgs that contains the event data.</param>
protected override void OnKeyDown(KeyEventArgs e)
{
if(e.KeyValue == 8 || e.KeyValue == 46)
_updateValues = true;
base.OnKeyDown (e);
}
/// <summary>
/// Overrides the KeyUp event.
/// </summary>
/// <param name="e">A System.Windows.Forms.KeyEventArgs that contains the event data.</param>
protected override void OnKeyUp(KeyEventArgs e)
{
if(e.KeyValue == 8 || e.KeyValue == 46)
_updateValues = false;
base.OnKeyUp (e);
}
That's it, thats all the code you need for creating your own custom AutoComplete TextBox Control. Compile your code once your finished and you have a user control you can add to any project where you need that functionality. Remember, if you like you can always add more functionality to your control, that is up to you.
I will be providing the control I created while creating my control and this tutorial. Remember though that this file is under the
GNU General Public License, meaning you can use or distribute it as you see fit, but the license header must stay intact. I hope you found this tutorial informative and useful, thank you for reading.
Happy Coding

PC_AutoComplete.zip ( 14.71k )
Number of downloads: 1632This post has been edited by PsychoCoder: 6 Oct, 2007 - 09:41 PM