This article shows how to create reusable and extendible spellchecking component based on NHunspell.
The history began about a year ago when I decided to write an application with spellchecking. I discovered lots of .NET components doing what I want, but all of them except NetSpell were proprietary. I looked through lots of C++ code while investigating how things work in other solutions. But then I understood that work is still in the starting phase and started with NetSpell to get some rough solution.
NetSpell is free spellchecking engine that was developed in 2004 for .NET.
The first goal was to add spellchecking functionality to
RichTextBox control. When I realized it, I was very proud of myself. It was working! But then, I needed to add such functionality to
MaskedTextBox control. I realized that my implementation was too sticky and I should spend almost the same time for integration to any new control. Implementation of such things should be at least as possible binding to any control or project. It must be self-sufficient. It should be some attachable thing that I can use any time I want it, like a book to check spelling and put it back on shelf.
Then I read some design patterns and began writing my implementation for any control use. I used Singleton for Instance property, and used it from any form to notify spellchecking engine about control it should check spelling. Then of course, I wanted to add some wavy underlining to the text and found WINAPI solution here. When I realized it, I was very proud of myself. My spellchecking engine worked for any text control and also could draw wavy underlines. But then I discovered that WINAPI underlining does not always work properly with different fonts. This was a crash. I desperately didn't want to draw those lines myself. But then another solution came to me from the skies here. It was describing how to draw those underlines overriding
WndProc method of control and adding some custom painting functionality.
Hunspell is a wrapper for commonly used Hunspell engine. Hunspell is C++ library that is used in such application as: OpenOffice, Mozilla Firefox/Thunderbird, Opera 10, The Bat! and many others.
After a couple of months, I discovered that there is a .NET wrapper for Hunspell. I looked into my last solution and found that if I want to add spellchecking to other projects, I still will have to copy lots of code with my hands. This was a crash. I understood that such things should be not only not binding a control, but also they should not be bound to the initial solution. The code must be reusable. If you write code with this thought in mind, your code can become more than just one time implementation for the same time. I needed to write a component that is possible to just include in my solution and use. The component should do everything it needs by self means and should expose as many events to satisfy every programmers' needs (I hand some problems using NetSpell because there were no events I desperately needed and I had to rewrite some code in existing library and recompile it).
I understand that some controls should handle underlining and some need not. So I described basic interface
ISpellingControl and then extended it with
IUnderlineable interface. Now I have
IUnderlineableSpellingControl interface for
richTextBox like controls and
ISpellingControl for all others.
As you can see in the first diagram,
NHunspellWrapper lives its own life and exposes
Instance property to others. We need only 1 instance per application. We can have as many
SpellingWorkers as we need.
SpellingWorker class has link to Editor it works with.
Attention: In different libraries, properties can have the same name and even declaration but in runtime, if you try to cast one type to other, property will return
Text property. In your code, you have written some class that works with
TextBoxBase that also has such property. If you try to cast one type to another and use its
Text property, it will return a
null value. So be careful with this.
The next diagram shows how you can solve this issue.
As I pointed before, we use Interface injection here. So now our component can work with most classes that have identical definitions through this interface. Anyway if
ClassA we want to use doesn't have the needed definitions, we should describe them for component to work with
To use the
SpellingWorker component, you will have to add reference to
NHunspellComponent to your project. Also add compatible dictionaries to your project and Hunspellx86/x64.dll and set your project properties post-build action to look like this:
xcopy "$(ProjectDir)Dictionaries" "$(TargetDir)" /ICRY
xcopy "$(ProjectDir)Hunspell" "$(TargetDir)" /ICRY
Drag the component onto the form and select the appropriate editor (that implements
IUnderlinableSpellingControl interfaces). That's it.
ISpellingControl in general is usually doesn't need much work because all properties and methods are already present in text editor control. So as you can see in the example application, we need to add this interface to
CustomMaskedTextBox and implement unique properties of
ISpellingControl only (those are
IsPassWordProtected). You can read about interface injection in Inversion of control and Dependency injection articles.
Spelling window should notify
NHunspellWrapper about the changes it makes. Also it should expose properties that will allow other classes to change its view.
Options are stored in spelling worker for each control. So you can setup individual options.
One of the main problems during spell check is notifying the user about the mistake just made. You can use any action you want. You can launch fireworks at each misspelled word on
UnderliningChanged event. But I prefer underlining them with red waves. So I describe how it was done in the sample application:
WndProc of control that implements
IUnderlinable was overridden to call
CustomPaint method used to manipulate bitmaps of editor's region. It draws wavy underlines under each item of
DrawWave method is used to draw a zigzag line from point to point.
I learned how to write components and the most irritating thing is that you have to do some dancing with tambourine to draw those wavy underlining we are so used to as users. They are not necessary conditions for components to work and are shown in
TestApplication project in
CustomPaintRichText class. You can see it as an example, but if you need something that really works, it needs to be well tested.
Also I had trouble with displaying
SpellingWorker component in Visual Studio toolbox.
The most clever thing about writing any library is to implement it as a reusable library, so you won't need to rewrite your code in future. Design patterns and practices will help you with this and of course solve problems again and again to gain experience. You won't learn anything from just reading articles unless you are a person with extrasensory perception.
This is the initial version of the article.
Updated source code