Enabling spell-checking in WPF and using a custom dictionary across the application

Basic spell checking

WPF included spell checking built in. You can enable it for individual textboxes as follows…

<TextBox SpellCheck.IsEnabled="True" />

However, it’s usually more useful to enable it across the entire application, which can be done by setting the property in App.xaml as follows…

<Application.Resources>
  <Style TargetType="TextBox">
    <Setter Property="SpellCheck.IsEnabled" Value="True" />
  </Style>
</Application.Resources>

WPF will use the machine’s language settings to determine the correct spelling, so those in the UK will see “color” marked as a typo (which of course it is!), whereas those in the US will see “colour” marked as a typo.

The original customer request was to enforce UK English spelling for all users, but this turned out to be extremely complex, and so we dropped the idea. I won’t go into the details, but will just comment that it’s not worth considering.

One issue we hit was that if we set a custom style on a textbox, then the spell checker style we had set was silently ignored. For example, we have a lot of textboxes with markup like this…

<TextBox Text="{Binding SomeProperty}">
  <TextBox.Style>
    <Style TargetType="TextBox">
      <Style.Triggers>
        <DataTrigger Binding="{Binding SomeProperty,
                               Converter={StaticResource SomeConverter}}"
    	                       Value="True">
          <Setter Property="BorderBrush"
    	    Value="Red" />
          <Setter Property="BorderThickness"
    	    Value="1" />
        </DataTrigger>
      </Style.Triggers>
    </Style>
  </TextBox.Style>
</TextBox>

This puts a red border around the textbox if the users hasn’t entered anything in it. Very useful for required fields. For some reason, when we had markup like this, the spell checking wasn’t working, even though the style set for that was nothing to do with any styles set on the control.

It turned out that we needed to modify the line that sets the target type (line 3, highlighted above) to the following…

<Style TargetType="TextBox"
       BasedOn="{StaticResource ResourceKey={x:Type TextBox}}">

This tells WPF to base the textbox’s styles on the regular textbox styles, and only then apply the custom styles here. Why on earth this isn’t the default is completely beyond me, but who am I to question the Ways Of The Wise?

Then it all ground to a halt

At this stage, a colleague discovered that enabling spell checking resulted in Visual Studio grinding to even more of a halt than usual when trying to run the application. A window that previously took 2-3 seconds to open could take up to a minute. Disabling spell checking fixed the issue. The odd thing was that we were working from the same code base, and it was working fine for me.

It turned out that custom dictionaries can be registered in the registry, and if there are invalid entries in there, Windows will spend a lot of time trying to work out what to do. For more details see this StackoverFlow answer. Great, all we need to do is fix the registry entry and we’re done. Even better, I even found some code that will clear it for you, enabling us to write a small console app that the support team could run on a client’s machine if they had the issue. Looks like we’re back in business.

Of course you know what’s coming don’t you? The aforementioned registry entry wasn’t there. Not even slightly. We spent some time digging around looking to see if it had moved (highly unlikely, as the blog post was only a few months old), but no joy.

Update: It seems that the issue only happens when running inside of VS. If she ran the deployed test version, or even ran her local code from the bin/Debug folder, it worked correctly. We ended up adding a key to the app.config file to allow us to disable spell checking if needed.

As of now, we haven’t solved this issue, but as it seems to be restricted to Visual Studio, we didn’t let it hold us up. Meanwhile, I went on to the next part of the feature…

Adding a custom dictionary

In a fit of enthusiasm, I had suggested to the customer that we could include a list of domain-specific words as a custom dictionary. Little did I know how painful this would be to implement!

You can add a custom dictionary fairly easily, it’s just a text file with a .lex extension. You can specify a culture on the first line, but we didn’t need that. The file was added with a Build Action of Resource, and “Copy to Output Directory” set to false. Setting the customer dictionary for a textbox is done like this…

Uri uri = new Uri("pack://application:,,,/CustomDictionary.lex");
tb.SpellCheck.CustomDictionaries.Add(uri);

…where tb is the textbox in question. The Uri points to the .lex file, which in this case was in the application root.

This is all very nice, but in a real application, you wouldn’t want to have to do this for every textbox individually, you’d want to set it once and have it work across the entire application. This is where I ran into problems.

The first of these was loading the custom dictionary for every textbox, without having to do each one individually, the other of which was that textboxes that are not visible when the window loads (say one on a non-default tab) will not have the custom dictionary applied by any code that walks the tree at initialisation. I asked about this on Stack Overflow, and got a promising looking answer. It used EventManager.RegisterClassHandler to register an event handler for any textboxes (or whatever type you specify) that may be created in the future. Sounds too good to be true! Well, it is, because it didn’t work 🙁

Investigating further, I discovered that someone else had asked the question, and found that there is a long-standing bug in EventManager.RegisterClassHandler that prevents it from firing for most controls.

In a comment to the answer to that question, someone posted a link to some code that uses an ugly hack to get the event to fire. As I was only interested in textboxes, I could simplify his code.

The end result was that I added the following code to my App.xaml.cs…

public partial class App {
  protected override void OnStartup(StartupEventArgs e) {
    base.OnStartup(e);
    EventManager.RegisterClassHandler(typeof(Window),
      FrameworkElement.SizeChangedEvent, new RoutedEventHandler(OnSizeChanged));
    EventManager.RegisterClassHandler(typeof(TextBox),
      FrameworkElement.LoadedEvent, new RoutedEventHandler(OnLoaded), true);
  }
  
  private static void OnSizeChanged(object sender, RoutedEventArgs e) {
    SetMyInitialised((Window)sender, true);
  }
  
  private static void OnLoaded(object sender, RoutedEventArgs e) {
    if (e.OriginalSource is TextBox) {
      TextBox tb = (TextBox)e.OriginalSource;
      Helpers.SetCustomDictionaryTextBox(tb);
    }
  }
  
  #region MyInitialised dependency property
  
  public static readonly DependencyProperty MyInitialisedProperty =
    DependencyProperty.RegisterAttached("MyInitialised",
      typeof(bool),
      typeof(App),
      new FrameworkPropertyMetadata(false,
        FrameworkPropertyMetadataOptions.Inherits,
        OnMyInitialisedChanged));
  
  private static void OnMyInitialisedChanged(DependencyObject dpo,
    DependencyPropertyChangedEventArgs ev) {
    if ((bool)ev.NewValue && dpo is FrameworkElement) {
      (dpo as FrameworkElement).Loaded += delegate { };
    }
  }
  
  public static void SetMyInitialised(UIElement element, bool value) {
    element.SetValue(MyInitialisedProperty, value);
  }
  
  public static bool GetMyInitialised(UIElement element) {
    return (bool)element.GetValue(MyInitialisedProperty);
  }
  
  #endregion MyInitialised
}

This code can be copied and pasted. The only bit you need to look at is line 17 in OnLoaded that sets the custom dictionary. That uses a method in a helper class..

public static class Helpers {
  private static readonly Uri Uri =
      new Uri("pack://application:,,,/CustomDictionary.lex");
 
  public static void SetCustomDictionaryTextBox(TextBox tb) {
    if (tb.SpellCheck.IsEnabled
        && tb.SpellCheck.CustomDictionaries.Count == 0) {
      tb.SpellCheck.CustomDictionaries.Add(Uri);
    }
  }
}

The extra check inside this method prevents the custom dictionary from being loaded for textboxes that have spell checking disabled, as well as preventing it being loaded more than once. It seems that every time a textbox becomes visible, say when you change tabs, the Loaded event fires again. As we only need the custom dictionary to be loaded once, we check before loading it.

So, I eventually got this working, with with an amazing amount of pain for such a seemingly simple request.

2 Comments

  1. Amir said:

    thanks it was very usefull
    i’m trying to add a context menu “Add to dictionary” for all textboxs wich add a word to my custom dictionary file. can you help?

    December 26, 2018
    Reply
    • Avrohom Yisroel Silver said:

      Hello,

      As far as I know, there isn’t a simple way to do it. You either have to build your own, or use a third-party component.

      This was a pain point, but as we use Telerik components already, we were able to use their spell check component, which includes a context menu.

      December 26, 2018
      Reply

Leave a Reply

Your email address will not be published.

This site uses Akismet to reduce spam. Learn how your comment data is processed.