netaholic
netaholic

Reputation: 1385

Load custom dictionary just once. Wpf spellchecker

So, here is the code:

IList dictionaries = SpellCheck.GetCustomDictionaries(tb);
Uri uri = new Uri("Russian.lex", UriKind.Relative);
dictionaries.Add(uri);
tb.SpellCheck.IsEnabled = true;

The thing is, that I have to create multiple textboxes with spellcheck, and the only way to assign a customdictionary is to pass the Uri to the TextBoxBase.CustomDictionaries.Add(). So every time I set SpellCheck.IsEnabled there is a 3-5 sec lag, which in my opinion is caused by loading file from the harddisk. On top of that it seems that every time the dictionary is loaded it stays in memory for eternity.

Any tips on how can I load custom dictionary once and than re-use it?

Upvotes: 3

Views: 2368

Answers (2)

John Melville
John Melville

Reputation: 3835

After many false starts, I think I have made it work.

I scrounged around with reflection until I found the internal representation (ILexicon) for the custom dictionary. I then assigned this ILexicon to each new text content. None of these interfaces are public, and so I run the obvious risk of being broken in future versions, or even a service pack, but it works for today. [This breaks and becomes unnecessary in .net 4.6 see below.]

using System;
using System.Collections;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Security;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using Melville.Library.ReflectionHelpers;

namespace PhotoDoc.Wpf.Controls.FormControls
{
  public static class DynamicDictionaryLoader
  {
    private static string dictionaryLocation;
    public static string DictionaryLocation
    {
      get
      {
        if (dictionaryLocation == null)
        {
          var path = Path.GetDirectoryName(Assembly.GetExecutingAssembly().GetName().CodeBase);
          dictionaryLocation = Path.Combine(path, "UsMedicalEnglish.dic");
        }
        return dictionaryLocation;
      }
    }

    public static void AddDictionary(TextBoxBase targetControl)
    {
      try
      {
        InnerLoadDictionary(targetControl);
      }
      catch (UriFormatException)
      {
        // occasionaly occurrs in odd instalations.  You just don't get the medical dictionary
      }
      catch (ArgumentException)
      {
        // this is a rare bug that I think may be a race condition inside WPF.  I've only triggered it 2-3 times.
        // One time was when I had seven files open at once.  The other, ironically, was when activating the
        // default text for a field.  

        // In this rare error exception, you will just get all your medical words flagged as mispellings for that
        // textbox.  I won't know for a while if this worked, because I only saw the bug after months of daily use
        // of the program.
      }
    }

    private static object oldDictionary = null;
    private static object dictionaryLoaderLock = new Object();

    private static void InnerLoadDictionary(TextBoxBase targetControl)
    {
      var dictionary = SpellCheck.GetCustomDictionaries(targetControl);
      if (dictionary.Count < 1)
      {
          var speller = dictionary.GetProperty("Speller");
          var uriMap = speller.GetProperty("UriMap") as IDictionary;
          if (uriMap.Count > 0) return; // already initalized
          lock (dictionaryLoaderLock)
          {
            var dictionaryUri = new Uri(DictionaryLocation, UriKind.Absolute);
            if (oldDictionary == null)
            {
              dictionary.Add(dictionaryUri);
              oldDictionary = uriMap.Values.OfType<object>().FirstOrDefault();
            }
            else
            {
              if (!(bool) speller.Call("EnsureInitialized")) return;
              uriMap.Add(dictionaryUri, oldDictionary);
              GetContext(
                speller.GetField("_spellerInterop").
                  GetField("_textChunk") as ITextChunk).
                  AddLexicon(oldDictionary.GetProperty("Lexicon") as ILexicon);
              speller.Call("ResetErrors");
            }
          }
      }
    }

    private static ITextContext GetContext(ITextChunk chunk)
    {
      ITextContext ret = null;
      chunk.get_Context(out ret);
      return ret;
    }

    #region Com Imports
    [ComImport]
    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    [Guid("549F997E-0EC3-43d4-B443-2BF8021010CF")]
    private interface ITextChunk
    {
      void stub_get_InputText();
      void stub_put_InputText();
      [SecurityCritical, SuppressUnmanagedCodeSecurity]
      void SetInputArray([In] IntPtr inputArray, Int32 size);
      void stub_RegisterEngine();
      void stub_UnregisterEngine();
      void stub_get_InputArray();
      void stub_get_InputArrayRange();
      void stub_put_InputArrayRange();
      void get_Count(out Int32 val);
      void get_Item(Int32 index, [MarshalAs(UnmanagedType.Interface)] out object val);
      void stub_get__NewEnum();
      [SecurityCritical]
      void get_Sentences([MarshalAs(UnmanagedType.Interface)] out object val);
      void stub_get_PropertyCount();
      void stub_get_Property();
      void stub_put_Property();
      [SecurityCritical, SuppressUnmanagedCodeSecurity]
      void get_Context([MarshalAs(UnmanagedType.Interface)] out ITextContext val);
      [SecurityCritical, SuppressUnmanagedCodeSecurity]
      void put_Context([MarshalAs(UnmanagedType.Interface)] ITextContext val);
      void stub_get_Locale();
      [SecurityCritical, SuppressUnmanagedCodeSecurity]
      void put_Locale(Int32 val);
      void stub_get_IsLocaleReliable();
      void stub_put_IsLocaleReliable();
      void stub_get_IsEndOfDocument();
      void stub_put_IsEndOfDocument();
      [SecurityCritical, SuppressUnmanagedCodeSecurity]
      void GetEnumerator([MarshalAs(UnmanagedType.Interface)] out object val);
      void stub_ToString();
      void stub_ProcessStream();
      void get_ReuseObjects(out bool val);
      [SecurityCritical, SuppressUnmanagedCodeSecurity]
      void put_ReuseObjects(bool val);
    }

    [ComImport]
    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    [Guid("B6797CC0-11AE-4047-A438-26C0C916EB8D")]
    private interface ITextContext
    {
      void stub_get_PropertyCount();
      void stub_get_Property();
      void stub_put_Property();
      void stub_get_DefaultDialectCount();
      void stub_get_DefaultDialect();
      void stub_AddDefaultDialect();
      void stub_RemoveDefaultDialect();
      [SecurityCritical, SuppressUnmanagedCodeSecurity]
      void get_LexiconCount([MarshalAs(UnmanagedType.I4)] out Int32 lexiconCount);
      [SecurityCritical, SuppressUnmanagedCodeSecurity]
      void get_Lexicon(Int32 index, [MarshalAs(UnmanagedType.Interface)] out ILexicon lexicon);
      [SecurityCritical, SuppressUnmanagedCodeSecurity]
      void AddLexicon([In, MarshalAs(UnmanagedType.Interface)] ILexicon lexicon);
      [SecurityCritical, SuppressUnmanagedCodeSecurity]
      void RemoveLexicon([In, MarshalAs(UnmanagedType.Interface)] ILexicon lexicon);
      void stub_get_Version();
      void stub_get_ResourceLoader();
      void stub_put_ResourceLoader();
      [SecurityCritical, SuppressUnmanagedCodeSecurity]
      void get_Options([MarshalAs(UnmanagedType.Interface)] out object val);
      void get_Capabilities(Int32 locale, [MarshalAs(UnmanagedType.Interface)] out object val);
      void stub_get_Lexicons();
      void stub_put_Lexicons();
      void stub_get_MaxSentences();
      void stub_put_MaxSentences();
      void stub_get_IsSingleLanguage();
      void stub_put_IsSingleLanguage();
      void stub_get_IsSimpleWordBreaking();
      void stub_put_IsSimpleWordBreaking();
      void stub_get_UseRelativeTimes();
      void stub_put_UseRelativeTimes();
      void stub_get_IgnorePunctuation();
      void stub_put_IgnorePunctuation();
      void stub_get_IsCaching();
      void stub_put_IsCaching();
      void stub_get_IsShowingGaps();
      void stub_put_IsShowingGaps();
      void stub_get_IsShowingCharacterNormalizations();
      void stub_put_IsShowingCharacterNormalizations();
      void stub_get_IsShowingWordNormalizations();
      void stub_put_IsShowingWordNormalizations();
      void stub_get_IsComputingCompounds();
      void stub_put_IsComputingCompounds();
      void stub_get_IsComputingInflections();
      void stub_put_IsComputingInflections();
      void stub_get_IsComputingLemmas();
      void stub_put_IsComputingLemmas();
      void stub_get_IsComputingExpansions();
      void stub_put_IsComputingExpansions();
      void stub_get_IsComputingBases();
      void stub_put_IsComputingBases();
      void stub_get_IsComputingPartOfSpeechTags();
      void stub_put_IsComputingPartOfSpeechTags();
      void stub_get_IsFindingDefinitions();
      void stub_put_IsFindingDefinitions();
      void stub_get_IsFindingDateTimeMeasures();
      void stub_put_IsFindingDateTimeMeasures();
      void stub_get_IsFindingPersons();
      void stub_put_IsFindingPersons();
      void stub_get_IsFindingLocations();
      void stub_put_IsFindingLocations();
      void stub_get_IsFindingOrganizations();
      void stub_put_IsFindingOrganizations();
      void stub_get_IsFindingPhrases();
      void stub_put_IsFindingPhrases();
    }

    [ComImport]
    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    [Guid("004CD7E2-8B63-4ef9-8D46-080CDBBE47AF")]
    internal interface ILexicon
    {
      void ReadFrom([MarshalAs(UnmanagedType.BStr)]string fileName);
      void stub_WriteTo();
      void stub_GetEnumerator();
      void stub_IndexOf();
      void stub_TagFor();
      void stub_ContainsPrefix();
      void stub_Add();
      void stub_Remove();
      void stub_Version();
      void stub_Count();
      void stub__NewEnum();
      void stub_get_Item();
      void stub_set_Item();
      void stub_get_ItemByName();
      void stub_set_ItemByName();
      void stub_get0_PropertyCount();
      void stub_get1_Property();
      void stub_set_Property();
      void stub_get_IsReadOnly();
    }
    #endregion
  }
}

This code relies on my reflection helper class:

using System;
using System.Linq;
using System.Reflection;

namespace Melville.Library.ReflectionHelpers
{
  public static class ReflectionHelper
  {
    public static void SetField(this object target, string property, object value)
    {
      Field(target, property).SetValue(target, value);
    }

    public static object GetField(this object target, string name)
    {
      return Field(target, name).GetValue(target);
    }

    private static FieldInfo Field(object target, string name)
    {
      return target.GetType().GetField(name, 
        BindingFlags.NonPublic | BindingFlags.Public |BindingFlags.GetField|
        BindingFlags.FlattenHierarchy|BindingFlags.Instance);
    }

    public static object GetProperty(this object target, string name)
    {
      return Property(target, name).
        GetValue(target);
    }

    private static PropertyInfo Property(object target, string name)
    {
      return target.GetType().GetProperty(name, 
        BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.GetProperty|BindingFlags.FlattenHierarchy|BindingFlags.Instance);
    }

    public static void SetProperty(this object target, string property, object value)
    {
      Property(target, property).SetValue(target, value);
    }

    private const BindingFlags MethodBindingFlags = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.InvokeMethod | BindingFlags.FlattenHierarchy | BindingFlags.Instance;

    private static MethodInfo Method(object target, string name, Type[] types)
    {
      return target.GetType().GetMethod(name,
        MethodBindingFlags, null, CallingConventions.Any, types, null) ;
    }

    public static MethodInfo Method(object target, string name)
    {
      var methodInfos = target.GetType().GetMethods(MethodBindingFlags);
      return methodInfos.FirstOrDefault(i=>i.Name.Equals(name, StringComparison.Ordinal));
    }

    public static object Call(this object target, string methodName, params object[] paramenters)
    {
      return Method(target, methodName, paramenters.Select(i => i.GetType()).ToArray()).Invoke(target, paramenters);
    }
  }
}

Upvotes: 3

123 456 789 0
123 456 789 0

Reputation: 10865

You can create a temporary file that contains your custom lex dictionary.

Implemented it this way where FileSystemHelper is just a helper class but it's just using Path and Directories class to create a File

public Uri CreateCustomLexiconDictionary()
{
 if (!_fileSystemHelper.Exists(_customDictionaryLocation))
 {
   _fileSystemHelper.AppendLine(_customDictionaryLocation, @"Line");
  }
  if ((_fileSystemHelper.Exists(_customDictionaryLocation) && customDictionaries.Count == 0))
                return new Uri("file:///" + _customDictionaryLocation);
  }
 }

From there each TextBoxes you use can just call CreateCustomLexiconDictionary and then add it to the Dictionaries object of the SpellCheck

IList dictionaries = SpellCheck.GetCustomDictionaries(tb);
dictionaries.Add(CreateCustomLexiconDictionary());

Once you are done you can remove the URI to the dictionary so that it cleans it up. Also delete the file once you are done.

Also, RichTextBox SpellCheck is really slow and Microsoft is aware of it.

Upvotes: 0

Related Questions