Click here to Skip to main content
Click here to Skip to main content

Spelling Suggestions in a WPF TextBox

, 25 Feb 2007
Rate this:
Please Sign up or sign in to vote.
Examines an intuitive way to correct typos in a TextBox.
Screenshot - SmartTextBox.png

Introduction

This article reviews a TextBox subclass which provides support for helping the user easily fix typos. The SmartTextBox extends WPF's built-in support for spellchecking by popping up a customizable list of "suggestions" for a misspelled word. The list of suggestions can be shown manually via the F1 key, or programmatically with one simple method call. When the list of suggestions is shown, its height is briefly animated to provide a subtle visual cue for the user.

SmartTextBox can give a WPF application that extra little touch of convenience and professionalism, which most users really appreciate. It might be appropriate for an application which allows the user to enter notes or comments that will be read by others.

Background

WPF provides support for spellchecking the text in a TextBox and RichTextBox. The SpellCheck class exposes the primary APIs used for spellchecking. As of WPF v1 the spellchecking support is still not full-featured, but it is expected to be more mature in subsequent releases.

For example, only a few languages are supported (I believe only English, German, and French), and you cannot use a custom dictionary. For more information about the spellchecking support, read more about it here.

The TextBox control is integrated with WPF's spellchecking feature. When spellchecking is enabled, a misspelled word is displayed with a red squiggly line beneath it. Also, if you right-click on a misspelled word the ContextMenu will provide spelling suggestions, as seen below:

The SmartTextBox's features complement this standard functionality.

Using the SmartTextBox

The API

SmartTextBox offers several public members you can use to control its behavior and appearance. Here is its public API:

Properties

  • AreSuggestionsVisible - Returns true if the list of suggestions is currently displayed.
  • IsCurrentWordMisspelled - Returns true if the word at the caret index is misspelled.
  • SuggestionListBoxStyle - Gets/sets the Style applied to the ListBox which displays spelling suggestions. This is a dependency property.

Methods

  • GetSpellingError() - Returns a SpellingError for the word at the current caret index, or null if the current word is not misspelled.
  • HideSuggestions() - Hides the list of suggestions and returns focus to the input area. If the list of suggestions is not already displayed, nothing happens.
  • ShowSuggestions() - Shows the list of suggestions for the word at the caret index. If the current word is not misspelled, this method does nothing.

The basics

You can create an instance of SmartTextBox in XAML the same way as any other element:

<jas:SmartTextBox>I contian a typo!</jas:SmartTextBox>

By default the spellchecking features are enabled. If you need to disable that functionality, you can do it several ways:

this.smartTextBox.SpellCheck.IsEnabled = false;

or

SpellCheck.SetIsEnabled( this.smartTextBox, false );

or (in XAML)

<jas:SmartTextBox SpellCheck.IsEnabled="false" />

Styling the list of suggestions

The list of spelling suggestions is shown in the SmartTextBox's adorner layer. The list itself is a ListBox control, which lives in an Adorner. If you want to stylize the list of suggestions, set the SmartTextBox's SuggestionListBoxStyle dependency property to a Style.

For example, here are the Styles used in the demo application, as seen in the screenshot at the top of the article:

<Style x:Key="SuggestionListBoxStyle" TargetType="ListBox">
  <Setter Property="BitmapEffect">
    <Setter.Value>
      <DropShadowBitmapEffect ShadowDepth="1" Softness="0.5" />
    </Setter.Value>
  </Setter>
  <Setter Property="BorderBrush" Value="Gray" />
  <Setter Property="ItemContainerStyle">
    <Setter.Value>
      <Style TargetType="ListBoxItem">
        <Setter Property="Border.BorderBrush" Value="LightGray" />
        <Setter Property="Border.BorderThickness" Value="0,0,0,0.5" />
        <Setter Property="FontWeight" Value="Bold" />
        <Setter Property="Margin" Value="2,4" />
        <Setter Property="FocusVisualStyle" Value="{x:Null}" />
      </Style>
    </Setter.Value>
  </Setter>
</Style>

<Style TargetType="jas:SmartTextBox">
  <Setter Property="AcceptsReturn" Value="True" />
  <Setter Property="BorderBrush" Value="Gray" />
  <Setter Property="Margin" Value="4" />
  <Setter
    Property="SuggestionListBoxStyle"
    Value="{StaticResource SuggestionListBoxStyle}" />
  <Setter Property="TextWrapping" Value="Wrap" />
</Style>

How it works

The remainder of this article discusses how the SmartTextBox works. You do not need to read further in order to use the control in your applications.

Adorner vs. Popup

When I first designed this class I tried to host the suggestions ListBox in a Popup. I ran into a couple of problems with using the Popup in this situation, so I decided to use a custom Adorner to host the suggestions instead.

The Popup did not "follow" the SmartTextBox when the window was moved. If I changed the window's state to 'Maximized' or 'Normal' the Popup would snap back into place, but not when the window moved. Adorners do not have that problem because they are not separate top-level windows, like the Popup.

Also, I encountered some frustrating issues relating to input focus when using a Popup. Those issues disappeared when I used the adorner approach.

The only drawback I'm aware of with using adorners to show the list of suggestions is that they can be clipped. If the bottom of the window which contains a SmartTextBox is very close to a misspelled word, the list of suggestions might not be entirely visible. I don't think this is too big of an issue, though. Here's what I'm referring to:

Showing the list of suggestions

When the caret is in a misspelled word and the user presses the F1 key, the SmartTextBox needs to display a list of suggested spellings. That is accomplished by the following (much abridged) code:

protected override void OnPreviewKeyDown( KeyEventArgs e )
{
 base.OnPreviewKeyDown( e );
 if( e.Key == Key.F1 )
 {
  this.AttemptToShowSuggestions();
  if( this.AreSuggestionsVisible )
   this.suggestionList.SelectedIndex = 0;
 }
}

void AttemptToShowSuggestions()
{
 if( this.AreSuggestionsVisible )
  return;

 // If there is no spelling error, there is no
 // need to show the list of suggestions.
 SpellingError error = this.GetSpellingError();
 if( error == null )
  return;

 this.suggestionList.ItemsSource = error.Suggestions;
 this.ShowSuggestions();
}

public SpellingError GetSpellingError()
{
 int idx = this.FindClosestCharacterInCurrentWord();
 return idx < 0 ? null : base.GetSpellingError( idx );
}

public void ShowSuggestions()
{
 if( this.AreSuggestionsVisible || !this.IsCurrentWordMisspelled )
  return;

 AdornerLayer layer = AdornerLayer.GetAdornerLayer( this );
 if( layer == null )
  return;

 // Position the adorner beneath the misspelled word.
 int idx = this.FindBeginningOfCurrentWord();
 Rect rect = base.GetRectFromCharacterIndex( idx );
 this.adorner.SetOffsets( rect.Left, rect.Bottom );

 // Add the adorner into the adorner layer.
 layer.Add( this.adorner );

 // Since the ListBox might have a new set of items but has not
 // rendered yet, we force it to calculate its metrics so that
 // the height animation has a sensible target value.
 this.suggestionList.Measure(
    new Size( Double.PositiveInfinity, Double.PositiveInfinity ) );
 this.suggestionList.Arrange(
    new Rect( new Point(), this.suggestionList.DesiredSize ) );

 // Animate the ListBox's height to the natural value.
 DoubleAnimation anim = new DoubleAnimation();
 anim.From = 0.0;
 anim.To = this.suggestionList.ActualHeight;
 anim.Duration = new Duration( TimeSpan.FromMilliseconds( 200 ) );
 anim.FillBehavior = FillBehavior.Stop;
 this.suggestionList.BeginAnimation( ListBox.HeightProperty, anim );

 this.areSuggestionsVisible = true;
}

Fixing a typo

When the user has selected a suggested word to replace a typo, the SmartTextBox needs to update the text and hide the list of suggestions. The heavy-lifting for most of that task is handled by the SpellingError class, as seen below:

void suggestionList_PreviewKeyDown( object sender, KeyEventArgs e )
{
 if( this.suggestionList.SelectedIndex < 0 )
  return;

 if( e.Key == Key.Escape )
 {
  this.HideSuggestions();
 }
 else if( e.Key == Key.Space || e.Key == Key.Enter || e.Key == Key.Tab )
 {
  this.ApplySelectedSuggestion();
  // Mark the event as handled so that the keystroke
  // does not propogate to the TextBox.
  e.Handled = true;
 }
}

void ApplySelectedSuggestion()
{
 if( !this.AreSuggestionsVisible || this.suggestionList.SelectedIndex < 0 )
  return;

 SpellingError error = this.GetSpellingError();
 if( error != null )
 {
  string correctWord = this.suggestionList.SelectedItem as string;
  error.Correct( correctWord );
  base.CaretIndex = this.FindEndOfCurrentWord();
  base.Focus();
 }
 this.HideSuggestions();
}

public void HideSuggestions()
{
 if( !this.AreSuggestionsVisible )
  return;

 this.suggestionList.ItemsSource = null;

 AdornerLayer layer = AdornerLayer.GetAdornerLayer( this );
 if( layer != null )
  layer.Remove( this.adorner );

 base.Focus();
 this.areSuggestionsVisible = false;
}

Revision History

  • February 19, 2007 - Created article.
  • February 25, 2007 - Fixed a bug involving the inheritance of dependency property values into the ListBox shown in the SmartTextBox's adorner layer. Zhou Yong (aka Sheva) and Ian Griffiths helped find the solution. Sheva blogged about it here. The issue was resolved in this thread on the WPF Forum. I removed an incorrect statement about the relationship between an element's visual tree and its adorner layer from this article. I also updated the article's source code download.

License

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

Share

About the Author

Josh Smith
Software Developer (Senior) Cynergy Systems
United States United States
Josh creates software, for iOS and Windows.
 
He works at Cynergy Systems as a Senior Experience Developer.
 
Read his iOS Programming for .NET Developers[^] book to learn how to write iPhone and iPad apps by leveraging your existing .NET skills.
 
Use his Master WPF[^] app on your iPhone to sharpen your WPF skills on the go.
 
Check out his Advanced MVVM[^] book.
 
Visit his WPF blog[^] or stop by his iOS blog[^].
Follow on   Twitter

Comments and Discussions

 
GeneralPopup vs Adorner PinmemberMGutman27-May-09 5:09 
RantSeems buggy to me Pinmemberldcr30-Apr-09 11:11 
I just downloaded it, and had a quick play.
 
If you replace a word from the sample, it recalculates the remainder of the words, and sometimes gets confused about where a word starts and ends - so it can end up replacing only parts of words with suggestions (e.g. i first click on 'profesional', replace with 'professional', then suddenly 'particularly' gets underlined, and if i then choose 'particular' I end up with 'particulararly' which is immediately underlined when i click elsewhere). It doesn't get better after that
Generalcool Pingroupzhujinlong1984091322-Apr-09 23:04 
QuestionDefining mispelt words PinmemberPaul Coldrey11-Nov-08 13:53 
AnswerRe: Defining mispelt words PinmvpJosh Smith12-Nov-08 8:12 
QuestionWindow maximized, size change, etc Pinmembercf200622-Aug-08 8:00 
Questiononly for VISTA? PinmemberBoniolopez27-Feb-07 0:57 
AnswerRe: only for VISTA? PinmvpJosh Smith27-Feb-07 2:01 
GeneralSpellcheck for any platform [modified] PinmemberBoniolopez27-Feb-07 3:21 
AnswerRe: only for VISTA? Pinmemberyassir.27-Mar-08 6:29 
GeneralGreat article...But I have a question PinmemberSven Cipido26-Feb-07 20:49 
GeneralRe: Great article...But I have a question PinmvpJosh Smith27-Feb-07 1:58 
AnswerRe: Great article...But I have a question PinmemberAdrian Betschart27-Feb-07 5:01 
GeneralRe: Great article...But I have a question PinmvpJosh Smith27-Feb-07 5:17 
GeneralSmart & Delicious...the two things I like in a control. PinmemberCapt Phunkosis20-Feb-07 8:39 
GeneralRe: Smart & Delicious...the two things I like in a control. PinmvpJosh Smith20-Feb-07 8:58 
GeneralGreat article! PinprotectorMarc Clifton20-Feb-07 5:20 
GeneralRe: Great article! PinmvpJosh Smith20-Feb-07 6:21 
GeneralRe: Great article! PinprotectorMarc Clifton20-Feb-07 10:36 
GeneralRe: Great article! PinmvpJosh Smith20-Feb-07 10:52 
GeneralRe: Great article! PinprotectorMarc Clifton20-Feb-07 11:22 
GeneralRe: Great article! PinmvpJosh Smith20-Feb-07 12:11 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web03 | 2.8.140827.1 | Last Updated 26 Feb 2007
Article Copyright 2007 by Josh Smith
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid