I've got a C# WPF RichTextBox
that allows for ScaleX
and ScaleY
LayoutTransform
adjustments via a Slider
. Unfortunately, this scaling can cause the caret to stop rendering, a bug that can be fixed per the code at this SO post here. More unfortunately, setting the caret's RenderTransform
causes the spell check red squiggly lines to stop showing up as you type. It seems as though unfocusing the RichTextBox
and focusing it again by clicking on the Slider
will cause all of the red squiggly lines to reappear. You can view a demo of this bug on my GitHub here.
Question: How can I cause the red squiggly spell check lines to show up as the user types while still allowing for RichTextBox
scaling and a fully-rendered-at-all-scale-levels caret? I've tried manually calling GetSpellingError(TextPointer)
, and this works...sort of. It isn't fully reliable unless I call GetSpellingError
on every word of the RichTextBox
, which is very slow to calculate when there is a large amount of content. I've also attempted to use reflection and such on items within the Speller
and related internal classes, such as Highlights
, SpellerStatusTable
, and SpellerHighlightLayer
. When looking at SpellerStatusTable
's runs list (which appear to have info on if runs are clean or dirty), the runs are not updated to contain errors until the slider is clicked, which implies that the RichTextBox
is not re-checking for spelling errors.
Commenting out caretSubElement.RenderTransform = scaleTransform;
in CustomRichTextBox.cs
"fixes" the problem but then breaks the caret rendering again.
Code --
MainWindow.xaml:
<Window x:Class="BrokenRichTextBox.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:BrokenRichTextBox"
mc:Ignorable="d"
Title="Rich Text Box Testing" Height="350" Width="525">
<Grid Background="LightGray">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<Slider Name="FontZoomSlider" Grid.Row="0" Width="150" Value="2" Minimum="0.3" Maximum="10" HorizontalAlignment="Right" VerticalAlignment="Center"/>
<local:CustomRichTextBox x:Name="richTextBox"
Grid.Row="1"
SpellCheck.IsEnabled="True"
ScaleX="{Binding ElementName=FontZoomSlider, Path=Value}"
ScaleY="{Binding ElementName=FontZoomSlider, Path=Value}"
AcceptsTab="True">
<local:CustomRichTextBox.LayoutTransform>
<ScaleTransform ScaleX="{Binding ElementName=richTextBox, Path=ScaleX, Mode=TwoWay}"
ScaleY="{Binding ElementName=richTextBox, Path=ScaleY, Mode=TwoWay}"/>
</local:CustomRichTextBox.LayoutTransform>
<FlowDocument>
<Paragraph>
<Run>I am some sample text withhh typooos</Run>
</Paragraph>
<Paragraph>
<Run FontStyle="Italic">I am some more sample text in italic</Run>
</Paragraph>
</FlowDocument>
</local:CustomRichTextBox>
</Grid>
</Window>
CustomRichTextBox.cs:
using System;
using System.Reflection;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Threading;
namespace BrokenRichTextBox
{
class CustomRichTextBox : RichTextBox
{
private bool _didAddLayoutUpdatedEvent = false;
public CustomRichTextBox() : base()
{
UpdateAdorner();
if (!_didAddLayoutUpdatedEvent)
{
_didAddLayoutUpdatedEvent = true;
LayoutUpdated += updateAdorner;
}
}
public void UpdateAdorner()
{
updateAdorner(null, null);
}
// Fixing missing caret bug code adjusted from: https://stackoverflow.com/questions/5180585/viewbox-makes-richtextbox-lose-its-caret
private void updateAdorner(object sender, EventArgs e)
{
Dispatcher.BeginInvoke(new Action(() =>
{
Selection.GetType().GetMethod("System.Windows.Documents.ITextSelection.UpdateCaretAndHighlight", BindingFlags.NonPublic | BindingFlags.Instance).Invoke(
Selection, null);
var caretElement = Selection.GetType().GetProperty("CaretElement", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(Selection, null);
if (caretElement == null)
return;
var caretSubElement = caretElement.GetType().GetField("_caretElement", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(caretElement) as UIElement;
if (caretSubElement == null) return;
// Scale slightly differently if in italic just so it looks a little bit nicer
bool isItalic = (bool)caretElement.GetType().GetField("_italic", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(caretElement);
double scaleX = 1;
if (!isItalic)
scaleX = (1 / ScaleX);
else
scaleX = 0.685;// output;
double scaleY = 1;
var scaleTransform = new ScaleTransform(scaleX, scaleY);
caretSubElement.RenderTransform = scaleTransform; // The line of trouble
}), DispatcherPriority.ContextIdle);
}
public double ScaleX
{
get { return (double)GetValue(ScaleXProperty); }
set { SetValue(ScaleXProperty, value); }
}
public static readonly DependencyProperty ScaleXProperty =
DependencyProperty.Register("ScaleX", typeof(double), typeof(CustomRichTextBox), new UIPropertyMetadata(1.0));
public double ScaleY
{
get { return (double)GetValue(ScaleYProperty); }
set { SetValue(ScaleYProperty, value); }
}
public static readonly DependencyProperty ScaleYProperty =
DependencyProperty.Register("ScaleY", typeof(double), typeof(CustomRichTextBox), new UIPropertyMetadata(1.0));
}
}