Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / VB

NHunspellToolTip- A Spellchecking ToolTip using Hunspell for .NET

4.86/5 (13 votes)
17 Feb 2010CPOL5 min read 27.4K   974  
This ToolTip will allow you to spell-check the text of items or controls when you can't see the whole text.

toolTipScreenShot.JPG

Background

As programmers, we are often asked to develop an interface to allow easier access to databases. In my case, I was dealing with a database structured using a snowflake schema. Certain tables held indices relating to other tables that contained the data. As an example, there were three levels of "Actions": "Objectives", "Recovery Actions", and "Action Steps". Any one of these could contain information on Priorities, Cost, Duration, Recovery Partners, and comments, along with the text of the action. To the human eye, the database was unreadable.

In developing the GUI for the user to access, it was unnecessary for the user to be able to modify all of the information from one screen. Instead, I wanted to provide a snapshot of the data to the user and use ToolTips to provide the more detailed information and ContextMenus for editing that information. The control that provided the information was a custom ListView (as seen below).

ScreenshotNoToolTip.jpg

This worked well, and the users found it easy to navigate through the database using the GUI. However, I soon realized that our users were not very careful while typing and produced a large number of spelling errors. So, I wanted to provide a way for the user to have a visual cue that they had made a spelling error.

To that end, I developed an IExtenderProvider that would extend TextBoxes to provide Microsoft Word style spell-as-you-type spell checking capabilities. I used Hunspell for .NET and created the NHunspellTextBoxExtender. I was able to use the TextBox extender when they were editing the text, but that still didn't provide any spell checking capability on the main form where the information was provided by ListViews.

So, the question was, how could I provide that visual cue that something was misspelled within the information? I was already using ToolTips to provide the information, so why not create a ToolTip that could spell-check its text and provide the visual cues that way? Then, the user could see that there was a spelling error, and go in and fix it. This custom ToolTip provides the functionality seen below:

toolTipScreenShot.JPG

The Spell Checking ToolTip

Unlike my previous control (the NHunspellTextBoxExtender), for this control I didn't need to use an IExtenderProvider since I was only changing one ToolTip. Instead, I chose to create a new Control that inherited ToolTip. The spell checking was very similar to the TextBox version. But because I was limiting it to display only, and because the control's text would only be set, and not appended or trimmed, I could pare down the SpellCheckControl.

SpellCheckControl

The SpellCheckControl handles all of the spell checking. When the ToolTip receives the Draw command, it calls the SetText method of the SpellCheckControl. The SetText method goes through all of the text and finds any misspelled words and identifies them using CharacterRanges.

The class structure is shown below:

VB
Imports System.Drawing
Imports System.IO
Imports System.Windows.Forms
Imports System.Reflection

Public Class SpellCheckControl
    Private FullText As String
    Private _Text(,) As String
    Public myNHunspell As Object = Nothing
    Private _spellingErrors() As String
    Private _spellingErrorRanges() As CharacterRange

    Public Sub New(ByRef HunspellObject As Object) 

    Public Sub SetText(ByVal Input As String)

    Private Function FindFirstLetterOrDigitFromPosition(_
                     ByVal SelectionStart As Long) As Long

    Private Function FindLastLetterOrDigitFromPosition(_
                     ByVal SelectionStart As Long) As Long

    Public Function GetSpellingErrorRanges() As CharacterRange()

    Public Function HasSpellingErrors() As Boolean

    Public Sub SetSpellingErrorRanges()

End Class

ToolTip Draw

Once the spelling errors have been identified (which is remarkably quick, thanks to NHunspell), the ToolTip then needs to be drawn. To do this, I simply handle the ToolTip_Draw method. At this point, I've already drawn the background using the e.DrawBackground() command. The next step is to draw the wavy red line. To do this, we first have to identify where the spelling errors are and determine where the red line should be drawn. This proved to be a bit tricky because we had to keep track of which line the current text was on.

But, once we had determined where to draw the line, we then had to actually draw it. I did this using some code that can be found at this blog. The full code of the Draw event is shown below:

VB
Private Sub NHunspellToolTip_Draw(ByVal sender As Object, _
        ByVal e As System.Windows.Forms.DrawToolTipEventArgs) _
        Handles Me.Draw
    e.DrawBackground()

    mySpellCheckControl.SetText(e.ToolTipText)

    'Now we do the custom drawing
    myBitmap = New Bitmap(e.Bounds.Width, e.Bounds.Height)
    bufferGraphics = Graphics.FromImage(myBitmap)
    bufferGraphics.Clip = New Region(e.Bounds)

    Dim currentRange As CharacterRange

    For Each currentRange In mySpellCheckControl.GetSpellingErrorRanges
        Dim startPoint, endPoint As Point
        Dim bottom, left, right As Integer

        'Determine which line the current word is on
        Dim lastNewline As Integer = 1
        For i = 1 To currentRange.First
            If Mid(e.ToolTipText, i, 1) = vbLf Then
                lastNewline = i + 1
            End If
        Next

        'Get the text of the line to the end of the word
        Dim lineToEndofWord As String = Mid(e.ToolTipText, lastNewline, _
                                            ((currentRange.First - lastNewline) + _
                                              currentRange.Length + 1))

        'Figure out how wide and tall the text before this is word is
        'Measure the text starting from the beginning 
        'to the end of the word to get the borrom coordinates
        Dim newSize As SizeF = _
          TextRenderer.MeasureText(Microsoft.VisualBasic.Strings.Left(e.ToolTipText, _
                                   (currentRange.First + currentRange.Length)), _
                                    e.Font, e.Bounds.Size, TextFormatFlags.Left)
        bottom = newSize.Height - 2

        'Now measure the text from the beginning of the current line 
        'to the end of the word to get the right coordinates
        newSize = TextRenderer.MeasureText(lineToEndofWord, e.Font, _
                  e.Bounds.Size, TextFormatFlags.Left)
        right = newSize.Width
        endPoint = New Point(right, bottom)

        'Now we can backtrack and find out how wide that text is
        newSize = TextRenderer.MeasureText(Mid(e.ToolTipText, _
                        currentRange.First + 1, currentRange.Length), _
                        e.Font, e.Bounds.Size, TextFormatFlags.Left)
        left = right - newSize.Width
        startPoint = New Point(left, bottom)

        startPoint.X += 2
        endPoint.X -= 4

        DrawWave(startPoint, endPoint)
    Next

    toolTipGraphics = e.Graphics

    toolTipGraphics.DrawImageUnscaled(myBitmap, 0, 0)

    e.DrawBorder()
    Dim stringFlags As New StringFormat()
    stringFlags.Alignment = StringAlignment.Near
    stringFlags.LineAlignment = StringAlignment.Near

    TextRenderer.DrawText(e.Graphics, e.ToolTipText, e.Font, _
                          e.Bounds, Color.Black, flags:=TextFormatFlags.Left)
End Sub

Using the Code

That's really it! This tool is very simple to use... simply download the DLL using one of the links above, and add it to one of your Toolboxes. The spell checking is done automatically, and this ToolTip can be used in the same way as the standard ToolTip.

Points of Interest

I was able to implement something different with this ToolTip. I am not sure why I could not get it to work with the NHunspellTextBoxExtender, but I am able to load this ToolTip without the NHunspell.dll file. This means that when I package up this tool, I do not need to include the NHunspell.dll as well. To accomplish this, I embed the NHunspell.dll file in the project. Then, when I create the Hunspell object, I create it using the raw assembly.

This takes a bit of work, and you have to know the paramaters before hand. To start, I have to load the assembly. This is done with a single line of code:

VB
Dim a As Assembly = Assembly.Load(My.Resources.NHunspell)

I then have to get the Type of the object that I am trying to create, and create a ConstructorInfo class. The constructor for the Hunspell object takes two strings. The code to do this takes a few more lines of code, but it's still relatively simple:

VB
Dim type_l As Type = a.GetType("NHunspell.Hunspell")
Dim types(1) As Type
types(0) = GetType(String)
types(1) = GetType(String)
Dim ctor As ConstructorInfo = type_l.GetConstructor(types)

The last thing that has to be done is to Invoke the constructor which will return an Object. To do this, I have to set up my parameters that will be passed in. The Invoke method takes an array of Objectss. The first parameter for the Hunspell is the .aff file and the second is the .dic file. To do this without any error checking looks like:

VB
Dim params(1) As Object
params(0) = USaff
params(1) = USdic

Dim result As Object = Nothing
result = ctor.Invoke(params)

When I do this for real, I place result = ctor.Invoke(params) into a Try/Catch block as I have to make sure that a couple of unmanaged DLLs are available. I have provided more information on that under my NHunspellTextBoxExtender article.

The only problem with this method is that the IntelliSense of Visual Studio will not work this way. That is why this step is not implemented until the very end. Before that, I include a reference to the NHunspell DLL which gives me my IntelliSense. Then, before finalizing it, I simply comment out that Import statement and change the Hunspell objects to Object.

History

  • 17 February 2010: Article created.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)