5,666,979 members and growing! (17,420 online)
Email Password   helpLost your password?
Languages » C# » General     Intermediate

AutoText V2.2, is a component that will complete the word as the user is writing on. Can be connected to a TextBox or a RichTextBox.

By Windmiller

A component that will find similar words in a lexicon as the user is writing on to make it easy to complete for the user. To use in Win32 graphical or Console applications.
C# 1.0, C# 2.0, C#Windows, .NET, .NET 3.0, .NET 1.0, .NET 1.1, .NET 2.0, Win2K, WinXP, Win2003, ASP.NET, WinForms, WebForms, VS.NET2002, VS.NET2003, Visual Studio, Design, Architect, Dev

Posted: 7 Jun 2007
Updated: 26 Nov 2007
Views: 19,109
Bookmarked: 54 times
Announcements
Loading...



Search    
Advanced Search
Sitemap
20 votes for this Article.
Popularity: 4.68 Rating: 3.60 out of 5
3 votes, 15.0%
1
0 votes, 0.0%
2
3 votes, 15.0%
3
5 votes, 25.0%
4
9 votes, 45.0%
5
Note: This is an unedited contribution. If this article is inappropriate, needs attention or copies someone else's work without reference then please Report This Article

Screenshot - classv22.gif

This is the class diagram V2.2, a new version.

Screenshot - scrv21.gif

Screenshot from the client application, this application only shows how to handle the components.

If you start writing you will see similar words to the one you´re writing in a floating listbox, if the word you are writing exist in the lexicon ofcourse.

-

Introduction

I am developing a three tier system with forms to fill on the client layer. But I´ve felt that I had to create some kind of component to keep track of words that the user wrote earlier. If the user wrote a city name in a textfield for an example and later needs to write it again, the component should give the user a list of similars to chose between. I want the component to be easy to implement in a system for the developer as I found out that it is. I´ve tested this component with 120.000 words and it was running fine. I have also used it with several textfields, it can handle one ore more TextBoxes and/or RichTextBoxes.

-

Using the code

This components are in disaggregates in diffrent namespaces. The namespace TextHelper contains the TextBoxHelper which is an object to connect a TextBox or a RichTextBox to through the property "connectTextBox" which handles both a TextBox and a RichTextBox because of the TextBoxBase. This Helper will automatically handle all the dirty work for the client application. To this object you need to connect a ListBox through the property "connectFloater" which will automatically fill and display the listbox with similar words to the one beeing written. And last, you need to connect the AutoText object to this object to be able to find the similar words. This object is in a second namespace within the name TextEngine, Why this? If you want to use the TextEngine in a console application as you´re developing and don´t need the TextBox part as you do if you´re developing a Win32 graphical application. This component exports the lexicon to a file when needed and also imports from one. Now in the client application, to release the functions as the TextBoxHelper will bring, do this:

using TextHelper;
using TextHelper.TextEngine; //Using both, cuz this is an Win32 Application.


class Client ...
//Our working objects!

private ITextBoxHelper tH;
private IAutoText sT;
...

public Client()
{
 InitializeComponent();

 //Initialize some stuff.

 //Maximum 20 lines of text in the floating listbox.

 this.tH = new TextBoxHelper(20); 
 this.sT = new AutoText();    //As handles all the words.

 this.tH.connectSimpleText = sT; Connect the AutoText to the TextBoxHelper.
 this.tH.connectFloater = listBox1; //Connect the listbox.

 this.tH.connectTextBox = textBox1; //Connect the textbox, user text writing.

}

Back to top?


Now, add a TextBox or a RichTextBox (or more if you like) to your client application. A ListBox and a button to add words, and one to export and a third to import, you don´t have to do anything with the eventhandlers for the TextBox/RichTextBox or the ListBox. The TextHelper object will automatically add these to them when the TextBox and ListBox are connected. But you´ll need an event for the button to add words to the lexicon, and for the export and import buttons. And in the button event function to add words, you add these lines:
private void button1_Click(object sender, System.EventArgs e)
{
 //Here we add a new word to the lexicon.

 //It adds all new words written in the textbox!

 if(textBox1.Text!=String.Empty)
 {
  this.tH.addWord();
  button1.Text = "Submit new word to the lexicon" + " :"
   + this.sT.Count.ToString();
 }
}
//And you´ll need these to be able to import or export!

//

private void button2_Click(object sender, System.EventArgs e)
{
 this.tH.exportToFile();
}
private void button3_Click(object sender, System.EventArgs e)
{
 this.tH.importFromFile();
 button1.Text = "Submit new word to the lexicon" + " :" +
 this.sT.Count.ToString();
}

Back to top?


Now here is the Interface ITextBoxHelper as the TextBoxHelper object does inherit from..


using System;
using System.Windows.Forms;
using TextHelper.TextEngine;

namespace TextHelper
{
 public interface ITextBoxHelper
 {
  ListBox fillList (ref ListBox list_to_fill); 
  bool exportToFile (); //Exports it all through the AutoText object.

  bool importFromFile (); //Imports..

  void addWord ();
  TextBoxBase connectTextBox {set;}
  ListBox connectFloater {set;}
  IAutoText connectSimpleText {set;}
  string getWritten {get;}
 }
} 

Back to top?


The TextBoxHelper Class that inherit from this interface, looks like this..


using System;
using System.Drawing;
using System.Windows.Forms;
using TextHelper.TextEngine;

namespace TextHelper
{
 public class TextBoxHelper: ITextBoxHelper
 {
  private ListBox mFloater; //Displaying all similars.

  private TextBoxBase mTextBox;    //Users textbox.

  private IAutoText mAT; //..

  private int mFloatLines; //How many lines to be able to see in listbox.

  private char mKey; //What we pressed, the latest.

  private string mWritten; //The text to search for.

  private const int ACTIVE_ON = 3; //When to react

 
  public TextBoxHelper(int float_lines){this.mFloatLines=float_lines;}

  //Connects a TextBox, from client

  //And connects event functions to it.

  public TextBoxBase connectTextBox
  {
   set
   {
    this.mTextBox = value;

    //Connect the eventhandlers to the TextBox.

    this.mTextBox.TextChanged += new EventHandler(my_box_TextChanged);
    this.mTextBox.KeyDown += new KeyEventHandler(this.my_box_KeyDown);
    this.mTextBox.GotFocus += new EventHandler(my_box_GotFocus);
   }
  }
  public ListBox connectFloater
  {
   set
   {
    this.mFloater = value;

    //Connect event functions to the ListBox.

    this.mFloater.KeyPress +=new KeyPressEventHandler(my_list_KeyPress);
   }
  }
  //Connects.

  public IAutoText connectSimpleText
  {
   set
   {
    if(this.mAT==null)
    {
     this.mAT = value;
    }
   }
  }

  ... some other functions ...

  //Exstract the word we are writing right now,

  //and see if we have similars.. with the autotext.autoFill

  private void my_box_TextChanged(object sender, System.EventArgs e)
  {
   this.hideSimilarsList ();

   int remend = ((TextBox)sender).SelectionStart;
   int remstart    = ((TextBox)sender).SelectionStart;

   for(int i=remend-1;i>=0;i--)
   {
    if(((TextBox)sender).Text[i]==' ')    //If we have a space

    {
     remstart = i+1; //Make the space excluded!

     break;
    }
    else if(i==0) //If we reatched the beginning, no space?

    {
     remstart = i; //We had no space, yo remember?

     break;
    }
    else if(((TextBox)sender).Text[i-1]=='\n')
    {//If we reatched the beginning on a new line, no space?

    
     remstart = i; //We had no space, yo remember?

     break;
    }
   }
   //The word we are writing, is comming up!

   this.mWritten = ((TextBox)sender).Text.Substring(remstart,remend-remstart);

   //Compare it with the lexicon.

   //But only if two letters!

   if(this.mWritten.Length>=ACTIVE_ON)
    this.mAT.autoFill(this.mWritten);
   
   //If floater is connected,

   //fill it up then.

   if(this.mFloater!=null&&this.mWritten.Length>=ACTIVE_ON)
    this.fillList(ref this.mFloater);
  }

  public void addWord()
  {
   this.mAT.addWord(this.mTextBox.Text);
  }

   ... some other functions ...

  private void my_list_KeyPress(object sender,
  System.Windows.Forms.KeyPressEventArgs e)
  {        
   //If we want to keep on typing.

   if((e.KeyChar>=32&&e.KeyChar<=255)||e.KeyChar==8)
   {
    //Remember what we pressed.

    this.mKey = e.KeyChar;
    this.mTextBox.Focus();
   }
   else if(e.KeyChar==13&&this.mFloater.Items.Count>0)
   {
    //We did press enter,

    //insert what we got.

    int sel_start = this.mTextBox.SelectionStart;
    int nr = this.mAT.getLetterHits();
    int end_pos    = sel_start + this.mFloater.
    SelectedItem.ToString().Remove(0,nr).Length;

    this.mTextBox.Text = this.mTextBox.Text.Insert
    (sel_start,this.mFloater.SelectedItem.ToString().
    Remove(0,nr));

    this.mTextBox.SelectionStart = end_pos;
    this.mTextBox.ScrollToCaret ();
    this.mTextBox.Focus ();
   }
   else
   {
    this.hideSimilarsList ();
    this.mTextBox.Focus ();
   }
  }
 }
}

This class is doing the Win32 app. work for you and the clients, it gets the text you´re currently writing and check this word with the lexicon through the AutoText object.

Back to top?

Following, we have the interface IAutoText:


using System;

namespace TextHelper
{
 namespace TextEngine //As you can see, this is in a box in a box.

 {
  public interface IAutoText
  {
   void addWord (string word_to_add); //Throws nothing.

   void addAndClean (string word_to_add);
   string autoFill (string text_to_match); //..

   string getNext (string text_to_match); //..

   bool exportToLexiconFile (string filename);//Throws Exception, but return.

   bool importFromLexiconFile(string filename);//Throws Exception, but return.

   int getLetterHits (); //Nothing

   string getWord {get;} //..

   int Count {get;}
  }
 }
}

Back to top?


And the class as inherit from the IAutoText:
using System;
using System.IO;
using System.Collections;

namespace TextHelper
{
 namespace TextEngine
 {
  public class AutoText: IAutoText
  {
   private StringList mWords; //All words, in the special arraylist.

   private int mRemember; 
   private int mSimilar; //The string in lexicon, closest to the written.

   private int mSimilars; //If we are not satisfied with the word. go on.

   private int mSimilarTree; //In which tree it resides.

   private const int SMALLEST_WORD = 5;
   private const int BIGGER = 2;

SMALLEST_WORD is there to limit a word to store only if the length is bigger than 4. BIGGER will limit words to display in the list having the length of the written word + 2 letters. We don't want to show words in the list that are only having one more letter than the written one.

   public AutoText()
   {
    this.mWords = new StringList();
    this.mSimilar = this.mSimilars = this.mSimilarTree = -1;
   }
   private int checkLetters(string written_word, string lexicon_word)
   {
    int letter_counter = 0;
    for(int i=0;i<written_word.Length;i++)
    {
     //If the letter_counter is less than written length,

     //go on matching letters in this word!

     if(letter_counter<written_word.Length)
     {
      //If the first letter is, no matter upper or lowercase,

      //the same as the one in lexicon, and the rest matter if upper/lower.

      if(lexicon_word[letter_counter]==written_word[letter_counter]||
      (letter_counter==0&&lexicon_word[letter_counter]
      .ToString().ToLower()
      ==written_word[letter_counter].ToString().ToLower()))
       letter_counter++;
      else return 0;
      }
     }
    return letter_counter;
   }
   public string autoFill(string text_to_match)
   {
    this.mSimilar = this.mSimilars = -1; //Forget the last episode!


    if(text_to_match!=String.Empty)
     if(((StringList)this.mWords[text_to_match.ToLower()[0]]).Count>0)
      return  this.autoText(text_to_match, 0);
     else
      return "";
    else return "";
   }
   public string getWord
   {
    get
    {
     if(this.mSimilar>=0)
      return ((StringList)this.mWords[this.mSimilarTree])
      [this.mSimilar].ToString();
     else return "";
    }
   }
   public string getNext(string text_to_match)
   {
    if(((StringList)this.mWords[text_to_match.ToLower()[0]]).Count>0)
    {
     int rememSim = this.mSimilars;
     int rmemeThr = this.mSimilarTree;
     string word = this.autoText(text_to_match, this.mSimilars+1);
     if(rememSim==this.mSimilars)
      this.mSimilars=-1;//Start over if we reatched the end of the lexicon.


     return  word;
    }
    else
     return "";
   }
   private string autoText(string text, int start)
   {
    int hits=0;

    for(int i=start;i<((StringList)this.mWords
    [text.ToLower()[0]]).Count;i++)
    {
     int len = ((StringList)this.mWords
     [text.ToLower()[0]])[i].ToString().Length;
     string lexicon_word = ((StringList)this.mWords
     [text.ToLower()[0]])[i].ToString();
     int hit = 0;

     //Do the matching thing..

     if(text.Length+BIGGER<=len)
     {
      hit = this.checkLetters(text,lexicon_word);
      if(hits<hit) //If longer than previous?

      {
       hits =  hit; //Pass it on.

       this.mSimilar = this.mSimilars = i; //And remember

       this.mSimilarTree = text.ToLower()[0]; //Remember tree.

       this.mRemember = hits; //Make it global,hits.'

      }
     }
    }
    if(this.mSimilar>=0)
     return ((StringList)this.mWords[this.mSimilarTree])
            [this.mSimilar].ToString();
    else return "";    //No match!        

   }

Back to top?

The following addWord(..) function is called when the user imports from a file, here we don´t need to check every word because the file should be ok.

   public void addWord(string word_to_add)
   {
    if(word_to_add.Length>=SMALLEST_WORD)
    this.mWords.Add (word_to_add); //Add a new word to the text engine.

   }

The following addAndClean(...) function is called when the user application adds a new single or some few words to the lexicon.

   public void addAndClean(string word_to_add)
   {    
    word_to_add = word_to_add.Replace('\t',' '); //Replace all tab spaces.

    word_to_add = word_to_add.Replace('\n',' '); //Replace all newlines.

    word_to_add    = word_to_add.Replace('\r',' '); //Replace all returns.

    string[]tempText = word_to_add.Split(' '); //Split em all!


    for(int i=0;i<tempText.Length;i++)
    {
     tempText[i] = tempText[i].TrimEnd(ridof);
     tempText[i] = tempText[i].TrimStart(ridof);

     if(tempText[i]!=String.Empty)//We don´t want to store blanks.

     {
      if(!this.checkIfExist(tempText[i]))
      {
       tempText[i] = tempText[i][0].ToString().ToLower()+
       tempText[i].Remove(0,1);
       this.addWord(tempText[i]);
      }
     }
    }
    this.mWords.Sort();
   }
   public bool exportToLexiconFile(string filename) //Throws Exception

   {
    try
    {
     using (StreamWriter sw = new StreamWriter(filename)) 
     {
      for(int i=0;i<this.mWords.Count;i++)
       for(int o=0;o<((StringList)this.mWords[i]).Count;o++)
        sw.WriteLine(((StringList)this.mWords[i])[o].ToString());
     }
     return true;
    }
    catch(Exception)
    {
     return false;
    }
   }
   public bool importFromLexiconFile(string filename) //Throws Exception

   {
    try
    {
     using (StreamReader sw = File.OpenText(filename)) 
     {
      string line="";
      while ((line = sw.ReadLine()) != null) 
      {
       this.addWord(line);
      }
      this.mWords.Sort();
     }
     return true;
    }
    catch(Exception)
    {
     return false;
    }
   }

Back to top?

Here is an intresting composition which inherit from ArrayList, to become like one. And from the IComparer to sort in string size order.

   private class StringList: ArrayList, IComparer //Inheritance

   {
    const int MAX_TREE = 255; //Dictionary letters.

    static bool mZeroStart = true; //To avoid stack overflow!


    int IComparer.Compare( object x, object y ) //Interface method.

    {
     if(x is string && y is string)
     {
      //Return the shortest string

      return( Comparer.Default.Compare(
       Convert.ToInt32(x.ToString().Length),
       Convert.ToInt32(y.ToString().Length)));
     }
     else
     {
      //If not string, return by default comparer.

      return( Comparer.Default.Compare( x,y));
     }
    }

The following function has to do something only once the first time and then acting like an empty normal constructor. The first object will be when the AutoText object creates an instance of StringList and here it needs to fill itself with more StringLists. Each StringList it creates will be unik for the first letter in a word creating an Indexing algorithm, which means that if you want to add a word beginning with an a. It will store this word at the index of the walue of the first letter in the word.

    //Constructor.. inherit function base()

    public StringList(): base()
    {
     //To stop it from StackOVerflow!

     if(mZeroStart==true)
     {
      mZeroStart = false; //Stop it from "loop call".

      for(int i=0;i<MAX_TREE;i++)
       base.Add(new StringList());

     }//Else acting like an ordinary constructor.

    }

Now we are going to not override but hide the base function with a new one. We want to be able to sort here when the caller decides to sort us.

    new public void Sort() //Hide base function with this new one!

    {
     for(int i=0;i<this.Count;i++)
      if(((ArrayList)this[i]).Count>0)
       ((ArrayList)this[i]).Sort(this);
    }

And, we will have to do the same to the following.

    new public int Add(object value) //Hide base function with this new one!

    {
     int result = ((ArrayList)this[value.ToString()[0]]).Add(value);
     return result;
    }
   }
  }
 }
}

Back to top?


This AutoText object is made so that you can use only this part (if you wish) with your console application, if you´ve decided to develop one. Or you can use the complete set even if you´re developing an ASP.NET Web project. The thought with this package was and is to remember text as the user has written erlier to give the user abillity to revert this text in a later occation.

Back to top?

-

Points of Interest

If you want more TextBoxes to deal with in your application? You will then have to add a TextBoxHelper and a ListBox to each textbox you add and connect them to its TextBoxHelper. Here you can either connect the same AutoText to every one of these TextBoxHelpers as then will bring you the text from the same lexicon. Or you can create several new AutoText lexicons to add to which one you want, you might want a diffrent language in another textbox or something. Pick 'n chose!

This is an updated version V2.2, this one can handle word by word matching. To see all functions and code, you´ll have to download it. I made this one more effective, as it loads and handles bigger word files than previous versions. This version uses Indexing on the first letter in the word. The versions before did search through every string in the whole lexicon for words beginning with the same letter as the written which was too heavy. Now it´s faster with larger lexicons. You use this code on your own risk, with no warranty, I´ve tested it with VisualStudio .NET 2003 on a XP driven PCIIII 3GHz, .NET 1.1. and with a Swedish textfile filled with 120.000 words. Also it is using a Comparing class to sort by string size.

Back to top?

-

History

Version 1.0, uploaded ? - this had only the sentence, and was machine core heavy.

Version 1.1, uploaded - 7 juni 2007 - this had both word and sentence matching, but still heavy.

Version 1.2, uploaded - 8 juni 2007 - made it easier for the processor.

Version 1.3, uploaded - 11 juni 2007 - made a client with multiline box, working.

Version 2.0, uploaded - 15 juni 2007 - removed the recursive calls, there was too many calls! And changed the client application so that it can handle both TextBox and RichTextBox. Reordered the classes in the namespaces, with new diffrent dependencies. Replaced the toolTip with a floating listbox to show all similar word in. And I rewrote this article.

Version 2.1, uploaded - 16 juni 2007 - Now it doesn´t matter if it´s lowercase or uppercase, inserted a function to import and export to file. And I´ve updated this article.

Version 2.2, uploaded - 20 juni 2007 - Now you´re able to import files big enough to handle over 120.000 words, and an Indexing search algorithm was implemented indexed on the first letter in the stored word.

Version 2.2, uploaded - 28 aug 2007 - Same as previous but a Visual Studio 2005 solution.

Version *, changed - 27 nov 2007 - Made some changes to the article.

Back to top?

-

License

This software is provided 'as-is' without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose including commercial applications. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required.

Back to top?

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

About the Author

Windmiller


Professional programmer, degree in Informatics and Applied Systems Science.
Location: Sweden Sweden

Other popular C# articles:

Article Top
Sign Up to vote for this article
You must Sign In to use this message board.
FAQ FAQ Noise ToleranceSearch Search Messages 
 Layout  Per page   
 Msgs 1 to 12 of 12 (Total in Forum: 12) (Refresh)FirstPrevNext
GeneralVs2005memberGerhardKreuzer10:47 21 Aug '07  
QuestionRe: Vs2005memberWindmiller22:32 21 Aug '07  
AnswerRe: Vs2005memberGerhardKreuzer0:12 22 Aug '07  
AnswerRe: Vs2005memberWindmiller1:05 22 Aug '07  
NewsRe: Vs2005 SorrymemberWindmiller22:09 27 Aug '07  
NewsA VERSION FOR VS2005 IS UPLOADED NOW!memberWindmiller7:58 28 Aug '07  
GeneralSome suggestionsmemberMathiasW23:16 11 Jun '07  
AnswerRe: Some suggestionsmemberWindmiller23:59 11 Jun '07  
AnswerRe: Some suggestionsmemberMathiasW0:42 12 Jun '07  
AnswerRe: Some suggestionsmemberWindmiller1:02 12 Jun '07  
AnswerRe: Some suggestionsmemberMathiasW2:13 12 Jun '07  
GeneralI would like comments on why, when you rate this article.memberWindmiller22:11 11 Jun '07  

General General    News News    Question Question    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

PermaLink | Privacy | Terms of Use
Last Updated: 26 Nov 2007
Editor:
Copyright 2007 by Windmiller
Everything else Copyright © CodeProject, 1999-2008
Web18 | Advertise on the Code Project