65.9K
CodeProject 正在变化。 阅读更多。
Home

AutoText V2.2:一个在用户键入时进行单词补全的组件

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.23/5 (19投票s)

2007年6月7日

CPOL

7分钟阅读

viewsIcon

54470

downloadIcon

790

一个组件,它会在用户输入时在词典中查找相似的单词,以便用户轻松进行单词补全。可用于 Win32 图形或控制台应用程序。

Screenshot - classv22.gif

类图 V2.2,新版本

Screenshot - scrv21.gif

客户端应用程序的截图,此应用程序仅演示如何处理组件

如果您开始键入,并且键入的单词存在于词典中,您将看到与您键入的单词相似的单词在一个浮动列表框中显示。

引言

我正在开发一个三层系统,其中包含在客户端层上填写的窗体。但我觉得我必须创建某种组件来跟踪用户之前写过的单词。例如,如果用户在文本字段中输入了一个城市名称,后来需要再次输入,该组件应该为用户提供一个相似单词的列表供其选择。我希望该组件易于开发人员实现。我已经用 120,000 个单词测试过这个组件,运行良好。我还将其用于多个文本字段,它可以处理一个或多个 TextBox 和/或 RichTextBox。

使用代码

组件分布在不同的命名空间中。`TextHelper` 命名空间包含 `TextBoxHelper`,它是一个连接 `TextBox` 或 `RichTextBox` 到 `connectTextBox` 属性的对象,该属性由于 `TextBoxBase` 而同时处理 `TextBox` 和 `RichTextBox`。此助手将自动处理客户端应用程序的所有繁琐工作。为此对象,您需要通过 `connectFloater` 属性连接一个 `ListBox`,该列表框将自动填充并显示与正在键入的单词相似的单词的列表。最后,您需要将 `AutoText` 对象连接到此对象,以便查找相似的单词。此对象位于第二个命名空间 `TextEngine` 中。原因如下:如果您想在控制台应用程序中使用 `TextEngine`,而不需要 `TextBox` 部分,就像您开发 Win32 图形应用程序时那样。此组件在需要时将词典导出到文件,并从文件中导入。现在,在客户端应用程序中,要释放 `TextBoxHelper` 带来的功能,请执行此操作

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.
}

返回顶部?

现在,在您的客户端应用程序中添加一个 `TextBox` 或 `RichTextBox`(或更多,如果您愿意)。添加一个 `ListBox`,一个用于添加单词的 `Button`,一个用于导出的 `Button`,以及一个用于导入的 `Button`。您不必对 `TextBox` / `RichTextBox` 或 `ListBox` 的事件处理程序做任何事情。当 `TextBox` 和 `ListBox` 连接后,`TextHelper` 对象会自动将它们添加到其中。 但是,您将需要一个事件来处理添加单词到词典的按钮,以及处理导出和导入按钮的事件。在添加单词的按钮事件函数中,添加以下行

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();
}

返回顶部?

这是 `TextBoxHelper` 对象继承的 `ITextBoxHelper` 接口。

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;}
    }
}

返回顶部?

继承自此接口的 `TextBoxHelper` 类如下所示。

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 ();
            }
        }
    }
}

此类为您和客户端处理 Win32 应用程序的工作。它获取您当前正在键入的文本,并使用 `AutoText` 对象通过词典检查单词。

返回顶部?

下面显示的是 `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;}
    }
}
}

返回顶部?

以及继承自 `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` 用于限制单词长度,仅当单词长度为 4 或更长时才存储。`BIGGER` 将限制列表中显示的单词,使其仅包含长度比键入的单词长 2 个字母的单词。我们不希望在列表中显示与键入单词只多一个字母的单词。

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!        
}

返回顶部?

以下 `addWord(..)` 函数在用户从文件导入时调用。此处我们不需要检查每个单词,因为文件应该是没问题的。

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.
}

以下 `addAndClean(...)` 函数在用户应用程序向词典添加单个或少量新单词时调用。

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;
    }
}

返回顶部?

这是一个有趣的组合,它继承自 `ArrayList`。并且继承自 `IComparer` 以按字符串大小排序。

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));
        }
    }

以下函数只需要做一次,第一次执行,然后像一个空的普通构造函数一样工作。当 `AutoText` 对象创建 `StringList` 的实例时,它需要用更多的 `StringList` 来填充自己。它创建的每个 `StringList` 对于单词的第一个字母都是唯一的,从而创建了一个索引算法,这意味着如果您想添加一个以 a 开头的单词,它会将该单词存储在 a 值对应的索引处。

//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.
}

现在我们不覆盖,而是用一个新函数隐藏基函数。我们希望在调用者决定排序时在此处进行排序。

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);
}

我们还必须在以下内容中执行相同的操作。

new public int Add(object value)
//Hide base function with this new one!
{
    int result = ((ArrayList)this[value.ToString()[0]]).Add(value);
    return result;
}

返回顶部?

这个 `AutoText` 对象是为了您可以选择只使用这部分(如果您愿意)与您的控制台应用程序一起使用。或者,如果您正在开发 ASP.NET Web 项目,您可以使用完整集。这个包的初衷和想法是记住用户以前写过的文本,以便用户以后可以使用这些文本。

返回顶部?

兴趣点

如果您想在应用程序中处理更多 `TextBox`,您将需要为每个添加的 `TextBox` 添加一个 `TextBoxHelper` 和一个 `ListBox`。这里您可以选择将相同的 `AutoText` 连接到所有这些 `TextBoxHelper`,这将为您提供来自同一个词典的文本。或者,您可以创建几个新的 `AutoText` 词典添加到您想要的任何地方。您可能想在另一个 `TextBox` 中使用另一种语言,或者其他东西。自由选择!

这是 V2.2 的更新版本。此版本可以处理逐字匹配。要查看所有功能和代码,您需要下载它。我让这个版本更有效率,因为它加载和处理比以前版本更大的单词文件。此版本使用基于单词首字母的索引。之前的版本会搜索整个词典中所有以与键入单词相同的字母开头的字符串,这过于繁重。现在,对于更大的词典,它更快了。您可以自行承担使用此代码的风险,不提供任何保证。我已经在 XP 驱动的 PCIIII 3GHz 上使用 Visual Studio .NET 2003、.NET 1.1 和一个包含 120,000 个单词的瑞典语文本文件对其进行了测试。它还使用一个比较类按字符串大小进行排序。

返回顶部?

历史

  • 版本 1.0,上传日期 ? - 此版本仅支持句子,并且效率低下。
  • 版本 1.1,上传日期 - 2007 年 6 月 7 日 - 此版本同时支持单词和句子匹配,但仍然效率低下。
  • 版本 1.2,上传日期 - 2007 年 6 月 8 日 - 减轻了处理器的负担。
  • 版本 1.3,上传日期 - 2007 年 6 月 11 日 - 创建了一个带有多行框的客户端,并且可以工作。
  • 版本 2.0,上传日期 - 2007 年 6 月 15 日 - 删除了递归调用。调用太多了!并更改了客户端应用程序,使其能够同时处理 `TextBox` 和 `RichTextBox`。在命名空间中重新排序了类,带有新的不同依赖关系。用浮动列表框替换了 `ToolTip` 以显示相似的单词。并且我重写了这篇文章。
  • 版本 2.1,上传日期 - 2007 年 6 月 16 日 - 现在不区分大小写了,插入了一个导入和导出到文件的功能。并且我更新了这篇文章。
  • 版本 2.2,上传日期 - 2007 年 6 月 20 日 - 现在您可以导入足够大的文件来处理超过 120,000 个单词,并且实现了一个索引搜索算法,该算法基于存储单词的首字母进行索引。
  • 版本 2.2,上传日期 - 2007 年 8 月 28 日 - 与上一个版本相同,添加了一个 Visual Studio 2005 解决方案。
  • 版本 *,更改 - 2007 年 11 月 27 日 - 对文章进行了一些修改。

返回顶部?

许可证

本软件按“原样”提供,不附带任何明示或暗示的保证。在任何情况下,作者均不对因使用本软件而造成的任何损害负责。授予任何人使用本软件用于任何目的(包括商业应用)的许可。如果您在产品中使用此软件,将在产品文档中注明将表示赞赏,但并非必需。

返回顶部?

© . All rights reserved.