Reputation: 2104
I have a custom WPF control that inherits from RichTextBox. I'd like to be able to modify the FlowDocument of the richtextbox whenever the text is changed. For demonstration, let's say that we have this:
<MyCustomRichTextbox>
<FlowDocument>
<Paragraph>This is the first paragraph</Paragraph>
<Paragraph>And this is the second</Paragraph>
</FlowDocument>
</MyCustomRichTextbox>
and whenever the text is changed (e.g. someone types in the control), the entire containing paragraph is colored red.
It seems to me that two things need to happen in order to achieve this:
MyCustomRichTextbox.Document.Blocks
Unfortunately, the OnTextChanged method doesn't provide a way to get the Block that changed. I was able to use LINQ and the TextChangedEventArgs.Offset to get the block, but I'm concerned that this approach will yield unacceptable slowdowns with larger documents (since it must enumerate each block every time a character is typed). Is there a better way to get the containing paragraph?
I know I could cache a reference to the "Last modified block" and check if it's still the one being modified, but that wouldn't really help in a random access scenario.
Upvotes: 1
Views: 1754
Reputation: 63317
If I understand your problem correctly, you want to highlight the containing Paragraph of the current selection (the current position of the caret). So it's obviously that you have to get the containing Paragraph each time the Selection changes. Then you can just change the Foreground
to Brushes.Red
. Fortunately that the containing Paragraph seems to be referenced by the TextPointer and the process of finding it is nearly immediate. (a TextPointer has a property called Paragraph
). Here is the detailed code:
Paragraph p = null;
//Suppose rtb is the name of your RichtTextBox
private void UpdateContainingBlockState() {
if (p != rtb.Selection.Start.Paragraph){
if (p != null) p.Foreground = Brushes.Black;
p = rtb.Selection.Start.Paragraph;
if (p != null) p.Foreground = Brushes.Red;
}
}
//The SelectionChanged event handler for your RichTextBox
private void selectionChangedHandler(object sender, RoutedEventArgs e){
UpdateContainingBlockState();
}
The frequency of changing the Selection is fairly high (each time you press almost keys which can cause the selection changing). So if your document is large and you realize some poor performance while typing, it's time to switch to the next more complex code. You can also try using Threading approach (or using Task) to put the UpdateContainingBlockState()
call in another thread but be careful about cross-thread access. Here I use a different approach, the idea is call the UpdateContainingBlockState()
at the right time, that is when the actual selection change can jump between paragraphs. While typing normal printable characters, the selection will be always in the current paragraph (so we don't need to call UpdateContainingBlockState()
) unless when you type the Enter key. Generally we will call the method when user typing a control key (arrow keys, home, end, Enter, ...). We should also call the method if the RichTextBox gets focused and if user clicks mouse on the RichTextBox. You can see that almost the typed characters won't trigger calling the method so it will improve the performance much more than the code above (of course it may be realizable only when the document is large). Here is the detailed code:
//should add this using at the beginning
using System.Runtime.InteropServices;
[DllImport("user32")]
private static extern int MapVirtualKey(int ucode, int mapType);
//The KeyUp event handler for your RichTextBox
private void keyUp_Handler(object sender, KeyEventArgs e){
if (char.IsControl((char) MapVirtualKey(KeyInterop.VirtualKeyFromKey(e.Key),0x2)))
UpdateContainingBlockState();
}
//The PreviewMouseUp event handler for your RichTextBox
private void previewMouseUp_Handler(object sender, MouseButtonEventArgs e){
//UpdateContainingBlockState();
//Calling UpdateContainingBlockState() directly will cause a small issue
//So we use this instead
Task.Run(() => Dispatcher.Invoke( () => UpdateContainingBlockState()));
}
//The GotKeyboardFocus event handler for your RichTextBox
private void gotKeyboardFocus_Handler(object sender,
KeyboardFocusChangedEventArgs e){
UpdateContainingBlockState();
}
Upvotes: 1