Spell Checker
Priya is two weeks into rebuilding her firm's CRM client on WPF. The old WinForms tool let salespeople type follow-up notes against a customer record, and somewhere along the way the team grew dependent on its spell-check squiggles. The first beta of the WPF replacement went out without them. The first feedback ticket arrived within an hour: "Why does the new app think I cannot spell?" She has a demo on Friday.
She drops a WpfHtmlEditor onto the note-entry view, presses F5, and types recieve. A red squiggle appears under the word before her finger leaves the key. There was nothing to configure. The control ships with a US English dictionary embedded in the assembly -- no extra NuGet package, no native DLLs to xcopy, no setup file to ship to the field laptops.
<Window xmlns:editor="clr-namespace:SpiceLogic.HtmlEditor.WPF;assembly=SpiceLogic.HtmlEditor.WPF">
<editor:WpfHtmlEditor x:Name="NoteEditor" />
</Window>Two ways to check, and they coexist
The control offers two checking modes and both are live at once. The first is the inline squiggle Priya already saw -- live feedback as the user types. The second is the dialog walker that a sales rep can launch from the toolbar to march through every flagged word in a long note one at a time, the same flow Microsoft Word users expect. Configuration sits on the SpellCheckOptions property:
NoteEditor.SpellCheckOptions.FireInlineSpellCheckingOnKeyStroke = true;
NoteEditor.SpellCheckOptions.InlineSpellCheckDebounceMilliseconds = 300;The debounce setting matters once Priya thinks about her account managers pasting thousand-line meeting transcripts. Without it, every keystroke would trigger a re-scan. The default 300 ms window coalesces a burst of typing into a single check and keeps the editor responsive on the older Surface devices the field team still uses.

The Friday demo includes the Munich office
The day before the demo, Priya remembers the German subsidiary will be on the call. US English is not going to impress them. She downloads the OpenOffice German .dic and .aff pair, drops them next to the executable, and points the editor at them:
NoteEditor.SpellCheckOptions.DictionaryFile.DictionaryFilePath = @"Dictionaries\de_DE.dic";
NoteEditor.SpellCheckOptions.DictionaryFile.AffixFilePath = @"Dictionaries\de_DE.aff";
NoteEditor.SpellCheckOptions.SpellCheckLanguage = SpellCheckLanguage.German;The default value of SpellCheckLanguage is SameAsEditorLanguage, which means the spell checker tracks whatever the editor's Language dependency property is set to. For a multilingual CRM the cleaner path is to bind both to the user-profile language selector and let the same Window serve every locale.
The terminology problem
Demo goes well. Two weeks in production, a different problem surfaces: the sales team's product names -- Aerolyte, BluPak, QuantumRail -- get squiggled on every note. Priya enables the user dictionary so a rep can right-click BluPak, pick Add to Dictionary, and never see the squiggle on that word again. The full walk-through is on the User Dictionary page.

When the dictionary is somewhere else entirely
For the regulated-industries customer Priya onboards next quarter, the answer cannot be "ship a dictionary file." Their compliance team maintains an internal terminology service and every flagged-word check has to go through it. The editor accepts that too. Implement ISpellCheckerEngine, hand the editor an instance, and every spell call routes to that engine -- inline squiggles, dialog suggestions, and Add-to-Dictionary all flow through the same hook. See Using a Custom Spell-Check Engine.
Knowing when a pass finished
The final piece for the CRM is an audit log: every time a rep clicks Save Note, the firm wants a record that the note was spell-checked. Priya subscribes to SpellCheckCompleted, increments a counter on the view-model, and disables Save until the counter advances. The handler fires once per dialog-mode pass:
NoteEditor.SpellCheckCompleted += (sender, e) =>
{
_viewModel.LastSpellCheckUtc = DateTime.UtcNow;
_viewModel.SaveCommand.NotifyCanExecuteChanged();
};That covers the path from "drop the control on a window" to "compliance signed off." Each step that came up here has its own page in this section with the deeper API surface -- start there when the simple version stops being enough.