With the textbox spell check in WPF, it doesn't seem to check spelling for words that are capitalized. I understand that this is designed for acronyms and such so that it doesn't tag all those as misspellings and it's useful for that. However in my industry (Architecture and Engineering) there are many companies that write their notes using all capitals so that basically makes the spell checking useless...
Is there any way to change the spell checker to not ignore capitalized words?
You could simply toLowerCase()
the string before running it through the spellchecker.
Ok so I finally got time to take another stab at this and came up with a way based on Tom's answer (though it's not as simple as doing that). I will warn you it is definitely a hack... But for anyone else struggling with this it seems to work. Essentially the logic goes like this:
- Create a 'shadow' textbox that is never shown and set it's text to the lowercase version of the main textbox and enable spell checking.
- Search the shadow textbox for spelling errors based in code and get the start position and length of any misspells in the box.
- Find the relative position of the character indexes found to the main textbox.
- Use an adorner layer to underline in red any misspelled words.
- Update the text and re-run the above whenever the text changes or the size of the box changes.
I was a little worried about performance but I have quite a few textboxes in my application and I have not noticed any slowdown while using this. Here is the code:
In the textbox loaded handler:
//Create a shadow textbox to run spell check in - set text to the lower version of main text
TextBox tbSpell = new TextBox();
tbSpell.Text = tb.Text.ToLower();
//Enable spelling on the shadow box if we have spell checking enabled
tbSpell.SpellCheck.IsEnabled = tb.DataContext is KeynoteVM && (tb.DataContext as KeynoteVM).EnableSpelling;
//Set the shadow as a tag to the main textbox so we have access to it directly
tb.Tag = tbSpell;
//Adde handlers for size change or text change as we may need to update adorners
tb.SizeChanged += tb_SizeChanged;
tb.TextChanged += tb_TextChanged;
Size and Text change handlers simply call the update method that looks like this:
//Remove existing adorners
AdornerLayer lyr = AdornerLayer.GetAdornerLayer(tb);
if (lyr != null)
{
Adorner[] ads = lyr.GetAdorners(tb);
if (ads != null)
{
foreach (Adorner ad in lyr.GetAdorners(tb))
{ lyr.Remove(ad); }
}
}
//Get the shadow textbox from the tag property
TextBox tbSpell = tb.Tag as TextBox;
if (tbSpell == null || !tbSpell.SpellCheck.IsEnabled)
{ return; }
//Make sure we have the latest text
tbSpell.Text = tb.Text.ToLower();
//Loop to get all spelling errors starting at index 0
int indx = 0;
while (true)
{
//Find the spelling error
indx = tbSpell.GetNextSpellingErrorCharacterIndex(indx, LogicalDirection.Forward);
if (indx > -1)
{
//Have a match, get the length of the error word
int len = tbSpell.GetSpellingErrorLength(indx);
//Get a rectangle describing the position of the error to use for drawing the underline
Rect r = tb.GetRectFromCharacterIndex(indx);
Rect rEnd = tb.GetRectFromCharacterIndex(indx + len);
//Modify the rectangle width to the width of the word
r.Width = rEnd.Right - r.Right;
//Create an adorner for the word and set the 'draw location' property to the rectangle
AdornerSpell ad = new AdornerSpell(tb);
ad.drawLocation = r;
//Add the adorner to the textbox
AdornerLayer.GetAdornerLayer(tb).Add(ad);
//Increment the index to move past this word
indx += len;
}
else
{ break; }
}
Here is the code for the adorner class I created (it just underlines in red):
public class AdornerSpell : Adorner
{
public Rect drawLocation { get; set; }
public AdornerSpell(UIElement adornedElement) : base(adornedElement) { }
protected override void OnRender(System.Windows.Media.DrawingContext drawingContext)
{
drawingContext.DrawLine(new System.Windows.Media.Pen(new SolidColorBrush(Colors.Red), 1), drawLocation.BottomLeft, drawLocation.BottomRight);
}
}
Then of course if you are using right click to get suggestions you have to modify that to get the suggestions from the shadow textbox instead of the regular one.
The only quirk I have found is that since it is always checking the lower case it identifies things like I've and I'll as misspellings since the I should be capitalized. I'm sure there is a way around that too but I haven't tried yet.
Anyway it's not pretty for sure but it seems to work and I can't find anything better without fully creating a new spell checking system altogether... I'm still open to other suggestions if anyone has them but this is at least workable.
Edit:
Realized I could actually further reduce performance hits by telling it to update with a 'BeginInvoke' on the dispatcher to run it async. Now the text changed and size changed methods look like this:
Dispatcher.BeginInvoke(new Action(() => UpdateSpellingAdorners(sender as TextBox)));