1

If I globally enable spell checking in App.xaml...

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

...then I get red underlines and spell checking in all textboxes in the application, irrespective of where they are.

If I want to add a custom dictionary, then I have to use code similar to the one shown in this SO answer, and then call it as follows...

public MainWindow() {
  InitializeComponent();
  Loaded += (_, __) => Helpers.SetCustomDictionary(this);
}

(code for helper method shown lower down)

This works fine for textboxes that are shown when the window first loads, but if I have a tab control, and the default tab has a textbox, then the custom dictionary is not applied.

I tried calling Helpers.SetCustomDictionary(this) when the tab loaded, but that didn't work either. I can see that the method is called when the window loads, and my guess is that at that stage, the tab's contents haven't been created, so the method doesn't find them to set the custom dictionary.

The only thing I found that worked was calling it when the individual textbox itself was loaded. However, this is painful, as I have to do this for every single textbox individually.

Anyone know of a way to get the custom dictionary working for textboxes that are not visible when the window first loads?

Thanks

P.S. Here is the code for the helper method, which uses the FindAllChildren() method shown in the linked SO reply...

public static void SetCustomDictionary(DependencyObject parent) {
  Uri uri = new Uri("pack://application:,,,/CustomDictionary.lex");
  List<TextBox> textBoxes = new List<TextBox>();
  FindAllChildren(parent, ref textBoxes);
  foreach (TextBox tb in textBoxes) {
    if (tb.SpellCheck.IsEnabled && !tb.SpellCheck.CustomDictionaries.Contains(uri)) {
      tb.SpellCheck.CustomDictionaries.Add(uri);
    }
  }
}
Avrohom Yisroel
  • 8,555
  • 8
  • 50
  • 106

2 Answers2

1

There is probably a better solution but you could use OnStartup event of your App.xaml.cs to set the dictionary for every TextBox when it loads with a single event handler:

public partial class App : Application
{
    protected override void OnStartup(StartupEventArgs e)
    {
            base.OnStartup(e);
            EventManager.RegisterClassHandler(typeof(TextBox), FrameworkElement.LoadedEvent, new RoutedEventHandler(TextBox_OnLoaded));
    }

    private void TextBox_OnLoaded(object sender, RoutedEventArgs e)
    {
            // Set custom dictionary here.
    }
}
Dean Kuga
  • 11,878
  • 8
  • 54
  • 108
  • Thanks for the reply, but the `TextBox_OnLoaded` method never got called. Whilst trying to debug it, I added `TextBox3.Loaded += (_, __) => Debug.WriteLine("TextBox3.Loaded");` to the main window's ctor, and then the custom dictionary was set for TextBox3. Does this make sense to you? I don't really want to have to add a dummy cal to the Loaded event of every text box, rather defeats the purpose. Thanks again. – Avrohom Yisroel May 17 '18 at 20:13
  • I tried that, and it did work, but then I'm not really any better off than when I started. As I said in my original question, I'm trying to find a way to avoid having to do this for every single textbox individually. Thanks anyway. Any other ideas? – Avrohom Yisroel May 22 '18 at 13:12
  • How is it for every textbox individually when you have a single event handler for all textboxes? – Dean Kuga May 22 '18 at 15:10
  • Sorry, misread your comment. I thought you meant to add a hook to the loaded event of every textbox individually. I just reread your comment and realised what you meant. However, it didn't make any difference, even with your change the custom dictionary isn't loaded for any textboxes, whether visible or not. – Avrohom Yisroel May 22 '18 at 15:57
  • Just to add on, I tried doing the same call to `EventManager.RegisterClassHandler` for other controls, and the event handlers never got called, so it's not specific to the textboxes. I also tried using that line in a window ctor (as opposed to in App.xaml.cs) and again the event handler was never called. It seems the issue is nothing to do with the spell checker, etc, it's purely a matter of the event handlers not being called. Any ideas? Thanks again. – Avrohom Yisroel May 22 '18 at 16:18
  • Hmm, just seen this question (https://stackoverflow.com/questions/11455800/routed-event-class-handler-for-loadedevent-does-not-work-for-most-classes), in which he says that there's a known bug that `EventManager.RegisterClassHandler` doesn't get called for most controls. Seems I'm doomed! – Avrohom Yisroel May 22 '18 at 16:36
0

Whilst Dean Kuga's answer looked promising, it didn't work for me. After some more searching, it seems that there is a bug that prevents the Loaded event from being fired in most cases. In a comment to the answer to the SO question where I saw this mentioned, Marcin Wisnicki linked to some code he wrote that works around the issue.

As I only want this to work for textboxes, I simplified his code a little. In case it helps anyone, here is my simplified code...

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
}

Being English, I changed "Initialized" to "Initialised", but other than that, the DP code is the same.

Hope this helps someone.

Avrohom Yisroel
  • 8,555
  • 8
  • 50
  • 106