只需加载自定义词典一次。WPF 拼写检查器。

3

所以,这里是代码:

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

事实是,我需要创建多个带有拼写检查的文本框,而分配自定义字典的唯一方法是将Uri传递给TextBoxBase.CustomDictionaries.Add()。因此,每次设置SpellCheck.IsEnabled时都会出现3-5秒的延迟,我认为这是由于从硬盘加载文件所致。最重要的是,似乎每次加载字典后,它都会永久留在内存中。
您有什么建议可以让我加载自定义词典一次,然后重复使用它吗?

静态类?具有首次使用加载的实用类吗? - crashmstr
SpellCheckSpellCheck.CustomDictionaries是只读属性。尝试使用XamlWriter克隆带有加载的字典的static TextBox也会失败,字典会丢失。恕我直言,请在回答之前尽量研究一下这个问题好吗? - netaholic
1
尊敬的朋友,请在回答之前尽量先研究一下问题,这样做是出于对你的尊重。首先,这种行为完全没有体现出尊重。其次,@crashmstr在评论中花时间给你提供了建议,这并不是一个回答,所以他们并没有试图回答你的问题。第三,他们并不是在暗示你想象的那样,所以你自己编造了一个回答,并把责任推给了他们。这样的表现非常差劲,尤其是来自一个这个网站的初级成员。 - Sheridan
对不起,我把建议当作答案考虑了。请原谅我的错误。 - netaholic
请注意,您不需要在 .net 4.6 及更高版本中使用此解决方案。在 .net 4.6 中,将字典应用于任何文本框时,它会自动应用于每个文本框和富文本框。 - John Melville
2个回答

3

经过多次尝试,我认为我已经成功了。

我通过反射搜寻,找到了自定义词典的内部表示(ILexicon)。然后将这个ILexicon分配给每个新的文本内容。这些接口都不是公开的,因此我明显面临着在未来版本或服务包中被破坏的风险,但今天它可以工作。[这在.net 4.6中会被打破并变得不必要,请参见下文。]

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
  }
}

这段代码依赖于我的反射助手类:
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);
    }
  }
}

1
嗨,约翰! 感谢你分享解决方案。可惜我没有时间去检查它是否有效,而且这个问题对我来说早已过去了,因为我们正在使用DevExpress控件,他们提供了使用他们的拼写检查器与本地WPF文本框的功能。我会将你的帖子标记为答案。希望能帮到其他人。 - netaholic
1
请注意,您不需要在.NET 4.6及更高版本中使用此解决方案。在.NET 4.6中,将字典应用于任何文本框将自动应用于每个文本框和富文本框。 - John Melville

0
你可以创建一个临时文件,其中包含你的自定义词典。
实现方式如下,其中FileSystemHelper只是一个辅助类,但它仅使用PathDirectories类来创建一个File
public Uri CreateCustomLexiconDictionary()
{
 if (!_fileSystemHelper.Exists(_customDictionaryLocation))
 {
   _fileSystemHelper.AppendLine(_customDictionaryLocation, @"Line");
  }
  if ((_fileSystemHelper.Exists(_customDictionaryLocation) && customDictionaries.Count == 0))
                return new Uri("file:///" + _customDictionaryLocation);
  }
 }

从那里开始,您使用的每个TextBoxes只需要调用CreateCustomLexiconDictionary,然后将其添加到SpellCheckDictionaries对象中

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

一旦完成,您可以从字典中删除URI以进行清理。完成后还要删除文件。
此外,RichTextBox的拼写检查非常慢,微软已经意识到这个问题。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接