WPF .NET 4.0 输入时拼写检查控件?

发布于 2024-11-16 02:13:08 字数 216 浏览 2 评论 0 原文

我正在寻找 WPF 拼写检查控件(类似于内置功能,但支持更多词典)。我需要它具有即时输入功能(红色下划线)。与内置的 .NET 4.0 拼写检查相比,它还应该支持 4 种以上的语言(例如英语、西班牙语、德语、意大利语和俄语支持就更好了)。

我更喜欢该控件拥有 MIT 或 BSD 许可证,可以在商业 Windows 应用程序中使用。源代码会很棒,因为我想将拼写建议集成到我的自定义右键单击上下文菜单中。

I am looking for a WPF spell checking control (similar to the in-built functionality but with support for more dictionaries). I need it to have the as-you-type functionality (red underlining). It should also support more than 4 languages than the in-built .NET 4.0 spell check (e.g. English, Spanish, German, Italian and Russian language support would be great).

I prefer the control have a MIT or BSD license that can be used within a commercial Windows application. Source code would be great as I would like to integrate the spelling suggestions into my custom right click context menu.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

孤君无依 2024-11-23 02:13:08

我将 AvalonEditNHunspell 通过添加我自己的 SpellCheckerBehavior。可以在 github 找到此示例项目。它是这样实现的:

<avalonEdit:TextEditor
    xmlns:avalonEdit="http://icsharpcode.net/sharpdevelop/avalonedit"
    Name="editor">
    <avalonEdit:TextEditor.ContextMenu>
        <ContextMenu>
            <MenuItem Command="Undo" />
            <MenuItem Command="Redo" />
            <Separator/>
            <MenuItem Command="Cut" />
            <MenuItem Command="Copy" />
            <MenuItem Command="Paste" />
        </ContextMenu>
    </avalonEdit:TextEditor.ContextMenu>
    <i:Interaction.Behaviors>
        <local:SpellCheckerBehavior />
    </i:Interaction.Behaviors>
</avalonEdit:TextEditor>

...像这样修改我的 MainWindow 的构造函数:

public MainWindow()
{
    SpellChecker.Default.HunspellInstance = new Hunspell("German.aff", "German.dic");
    editor.TextArea.TextView.LineTransformers.Add(new SpellCheckerColorizer());
}

...并将以下类添加到我的项目中以集成它们:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Interactivity;
using System.Windows.Media;
using ICSharpCode.AvalonEdit;
using ICSharpCode.AvalonEdit.Document;
using ICSharpCode.AvalonEdit.Rendering;
using NHunspell;

namespace Martin
{
    public class SpellChecker
    {
        static Lazy<SpellChecker> defaultInstance = new Lazy<SpellChecker>(() => new SpellChecker());
        public static SpellChecker Default { get { return defaultInstance.Value; } }

        public Hunspell HunspellInstance { get; set; }

        public class Word
        {
            public int Index { get; set; }
            public string Value { get; set; }
        }

        static IEnumerable<Word> FindWords(string text)
        {
            foreach (Match m in new Regex(@"\w+").Matches(text))
            {
                yield return new Word() { Index = m.Index, Value = m.Value };
            }
        }

        public IEnumerable<Word> FindSpellingErrors(string text)
        {
            foreach (var word in FindWords(text))
            {
                if (!Spell(word.Value))
                {
                    yield return word;
                }
            }
        }

        public bool Spell(string word)
        {
            return HunspellInstance.Spell(word);
        }

        public List<string> Suggest(string word)
        {
            return HunspellInstance.Suggest(word);
        }
    }

    public class SpellCheckerBehavior : Behavior<TextEditor>
    {
        TextEditor textEditor;
        List<Control> originalItems;

        protected override void OnAttached()
        {
            textEditor = AssociatedObject;
            if (textEditor != null)
            {
                textEditor.ContextMenuOpening += new ContextMenuEventHandler(TextEditorContextMenuOpening);
                textEditor.TextArea.MouseRightButtonDown += new System.Windows.Input.MouseButtonEventHandler(TextAreaMouseRightButtonDown);
                originalItems = textEditor.ContextMenu.Items.OfType<Control>().ToList();
            }
            base.OnAttached();
        }

        protected override void OnDetaching()
        {
            if (textEditor != null)
            {
                textEditor.ContextMenuOpening -= new ContextMenuEventHandler(TextEditorContextMenuOpening);
                textEditor.TextArea.MouseRightButtonDown -= new System.Windows.Input.MouseButtonEventHandler(TextAreaMouseRightButtonDown);
                originalItems = null;
                textEditor = null;
            }
            base.OnDetaching();
        }

        void TextAreaMouseRightButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
        {
            var position = textEditor.GetPositionFromPoint(e.GetPosition(textEditor));
            if (position.HasValue)
            {
                textEditor.TextArea.Caret.Position = position.Value;
            }
        }

        void TextEditorContextMenuOpening(object sender, ContextMenuEventArgs e)
        {
            foreach (Control item in textEditor.ContextMenu.Items.OfType<Control>().ToList())
            {
                if (originalItems.Contains(item)) { continue; }
                textEditor.ContextMenu.Items.Remove(item);
            }
            var position = textEditor.TextArea.Caret.Position;
            Match word = null;
            Regex r = new Regex(@"\w+");
            var line = textEditor.Document.GetText(textEditor.Document.GetLineByNumber(position.Line));
            foreach (Match m in r.Matches(line))
            {
                if (m.Index >= position.VisualColumn) { break; }
                word = m;
            }
            if (null == word ||
                position.Column > word.Index + word.Value.Length ||
                SpellChecker.Default.Spell(word.Value))
            {
                return;
            }
            textEditor.ContextMenu.Items.Insert(0, new Separator());
            var suggestions = SpellChecker.Default.Suggest(word.Value);
            if (0 == suggestions.Count)
            {
                textEditor.ContextMenu.Items.Insert(0,
                    new MenuItem() { Header = "<No suggestions found>", IsEnabled = false });
                return;
            }
            foreach (string suggestion in suggestions)
            {
                var item = new MenuItem { Header = suggestion, FontWeight = FontWeights.Bold };
                item.Tag =
                    new Tuple<int, int>(
                        textEditor.Document.GetOffset(position.Line, word.Index + 1),
                        word.Value.Length);
                item.Click += ItemClick;
                textEditor.ContextMenu.Items.Insert(0, item);
            }
        }

        private void ItemClick(object sender, RoutedEventArgs e)
        {
            var item = sender as MenuItem;
            if (null == item) { return; }
            var segment = item.Tag as Tuple<int, int>;
            if (null == segment) { return; }
            textEditor.Document.Replace(segment.Item1, segment.Item2, item.Header.ToString());
        }
    }

    public class SpellCheckerColorizer : DocumentColorizingTransformer
    {
        private readonly TextDecorationCollection textDecorationCollection;

        public SpellCheckerColorizer()
        {
            textDecorationCollection = new TextDecorationCollection();
            textDecorationCollection.Add(new TextDecoration()
            {
                Pen = new Pen { Thickness = 1, DashStyle = DashStyles.Dot, Brush = new SolidColorBrush(Colors.Red) },
                PenThicknessUnit = TextDecorationUnit.FontRecommended
            });
        }

        protected override void ColorizeLine(DocumentLine line)
        {
            var lineText = CurrentContext.Document.Text
                .Substring(line.Offset, line.Length);
            foreach (var error in SpellChecker.Default.FindSpellingErrors(lineText))
            {
                base.ChangeLinePart(line.Offset + error.Index, line.Offset + error.Index + error.Value.Length,
                            (VisualLineElement element) => element.TextRunProperties.SetTextDecorations(textDecorationCollection));
            }
        }
    }
}

I combined AvalonEdit with NHunspell by adding my own SpellCheckerBehavior. An example project of this can be found at github. It is implemented like this:

<avalonEdit:TextEditor
    xmlns:avalonEdit="http://icsharpcode.net/sharpdevelop/avalonedit"
    Name="editor">
    <avalonEdit:TextEditor.ContextMenu>
        <ContextMenu>
            <MenuItem Command="Undo" />
            <MenuItem Command="Redo" />
            <Separator/>
            <MenuItem Command="Cut" />
            <MenuItem Command="Copy" />
            <MenuItem Command="Paste" />
        </ContextMenu>
    </avalonEdit:TextEditor.ContextMenu>
    <i:Interaction.Behaviors>
        <local:SpellCheckerBehavior />
    </i:Interaction.Behaviors>
</avalonEdit:TextEditor>

...modifying my MainWindow's constructor like this:

public MainWindow()
{
    SpellChecker.Default.HunspellInstance = new Hunspell("German.aff", "German.dic");
    editor.TextArea.TextView.LineTransformers.Add(new SpellCheckerColorizer());
}

...and adding the following classes to my project to integrate them:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Interactivity;
using System.Windows.Media;
using ICSharpCode.AvalonEdit;
using ICSharpCode.AvalonEdit.Document;
using ICSharpCode.AvalonEdit.Rendering;
using NHunspell;

namespace Martin
{
    public class SpellChecker
    {
        static Lazy<SpellChecker> defaultInstance = new Lazy<SpellChecker>(() => new SpellChecker());
        public static SpellChecker Default { get { return defaultInstance.Value; } }

        public Hunspell HunspellInstance { get; set; }

        public class Word
        {
            public int Index { get; set; }
            public string Value { get; set; }
        }

        static IEnumerable<Word> FindWords(string text)
        {
            foreach (Match m in new Regex(@"\w+").Matches(text))
            {
                yield return new Word() { Index = m.Index, Value = m.Value };
            }
        }

        public IEnumerable<Word> FindSpellingErrors(string text)
        {
            foreach (var word in FindWords(text))
            {
                if (!Spell(word.Value))
                {
                    yield return word;
                }
            }
        }

        public bool Spell(string word)
        {
            return HunspellInstance.Spell(word);
        }

        public List<string> Suggest(string word)
        {
            return HunspellInstance.Suggest(word);
        }
    }

    public class SpellCheckerBehavior : Behavior<TextEditor>
    {
        TextEditor textEditor;
        List<Control> originalItems;

        protected override void OnAttached()
        {
            textEditor = AssociatedObject;
            if (textEditor != null)
            {
                textEditor.ContextMenuOpening += new ContextMenuEventHandler(TextEditorContextMenuOpening);
                textEditor.TextArea.MouseRightButtonDown += new System.Windows.Input.MouseButtonEventHandler(TextAreaMouseRightButtonDown);
                originalItems = textEditor.ContextMenu.Items.OfType<Control>().ToList();
            }
            base.OnAttached();
        }

        protected override void OnDetaching()
        {
            if (textEditor != null)
            {
                textEditor.ContextMenuOpening -= new ContextMenuEventHandler(TextEditorContextMenuOpening);
                textEditor.TextArea.MouseRightButtonDown -= new System.Windows.Input.MouseButtonEventHandler(TextAreaMouseRightButtonDown);
                originalItems = null;
                textEditor = null;
            }
            base.OnDetaching();
        }

        void TextAreaMouseRightButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
        {
            var position = textEditor.GetPositionFromPoint(e.GetPosition(textEditor));
            if (position.HasValue)
            {
                textEditor.TextArea.Caret.Position = position.Value;
            }
        }

        void TextEditorContextMenuOpening(object sender, ContextMenuEventArgs e)
        {
            foreach (Control item in textEditor.ContextMenu.Items.OfType<Control>().ToList())
            {
                if (originalItems.Contains(item)) { continue; }
                textEditor.ContextMenu.Items.Remove(item);
            }
            var position = textEditor.TextArea.Caret.Position;
            Match word = null;
            Regex r = new Regex(@"\w+");
            var line = textEditor.Document.GetText(textEditor.Document.GetLineByNumber(position.Line));
            foreach (Match m in r.Matches(line))
            {
                if (m.Index >= position.VisualColumn) { break; }
                word = m;
            }
            if (null == word ||
                position.Column > word.Index + word.Value.Length ||
                SpellChecker.Default.Spell(word.Value))
            {
                return;
            }
            textEditor.ContextMenu.Items.Insert(0, new Separator());
            var suggestions = SpellChecker.Default.Suggest(word.Value);
            if (0 == suggestions.Count)
            {
                textEditor.ContextMenu.Items.Insert(0,
                    new MenuItem() { Header = "<No suggestions found>", IsEnabled = false });
                return;
            }
            foreach (string suggestion in suggestions)
            {
                var item = new MenuItem { Header = suggestion, FontWeight = FontWeights.Bold };
                item.Tag =
                    new Tuple<int, int>(
                        textEditor.Document.GetOffset(position.Line, word.Index + 1),
                        word.Value.Length);
                item.Click += ItemClick;
                textEditor.ContextMenu.Items.Insert(0, item);
            }
        }

        private void ItemClick(object sender, RoutedEventArgs e)
        {
            var item = sender as MenuItem;
            if (null == item) { return; }
            var segment = item.Tag as Tuple<int, int>;
            if (null == segment) { return; }
            textEditor.Document.Replace(segment.Item1, segment.Item2, item.Header.ToString());
        }
    }

    public class SpellCheckerColorizer : DocumentColorizingTransformer
    {
        private readonly TextDecorationCollection textDecorationCollection;

        public SpellCheckerColorizer()
        {
            textDecorationCollection = new TextDecorationCollection();
            textDecorationCollection.Add(new TextDecoration()
            {
                Pen = new Pen { Thickness = 1, DashStyle = DashStyles.Dot, Brush = new SolidColorBrush(Colors.Red) },
                PenThicknessUnit = TextDecorationUnit.FontRecommended
            });
        }

        protected override void ColorizeLine(DocumentLine line)
        {
            var lineText = CurrentContext.Document.Text
                .Substring(line.Offset, line.Length);
            foreach (var error in SpellChecker.Default.FindSpellingErrors(lineText))
            {
                base.ChangeLinePart(line.Offset + error.Index, line.Offset + error.Index + error.Value.Length,
                            (VisualLineElement element) => element.TextRunProperties.SetTextDecorations(textDecorationCollection));
            }
        }
    }
}
玩世 2024-11-23 02:13:08

AvalonEdit ( http://www.codeproject.com/KB/edit/AvalonEdit.aspx ) was written from the ground up in WPF for SharpDevelop. Oh the wonders of a quick Google.

And if you're fine with one language at a time (but toggable), you can use the default WPF functionality ( http://joshsmithonwpf.wordpress.com/2007/02/17/spellchecking-and-suggested-spellings-in-a-textbox/ )

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文