I am trying to display the main syntax highlighting in a WPF RichTextBox. This basically works, but the rendering performance is terrible.
At first I naively tried:
/// <summary> /// Main event handler for syntax highlighting. /// </summary> private void XmlChanged(object sender, TextChangedEventArgs e) { VM.Dirty = true; if (VM.Pretty) { var range = new TextRange(XmlView.Document.ContentStart, XmlView.Document.ContentEnd); Render(range.Text); } } /// <summary> /// Entry point for programmatically resetting the textbox contents /// </summary> private void Render(string text) { XmlView.TextChanged -= this.XmlChanged; if (VM.Pretty) { var tokens = tokenizer.Tokenize(text); Format(XmlView.Document, tokens); } XmlView.TextChanged += this.XmlChanged; } private void Format(FlowDocument doc, List<Token> tokens) { var start = doc.ContentStart; foreach (var token in tokens) { TextRange range = new TextRange(start.GetPositionAtOffset(token.StartPosition, LogicalDirection.Forward), start.GetPositionAtOffset(token.EndPosition, LogicalDirection.Forward)); range.ApplyPropertyValue(TextElement.ForegroundProperty, m_syntaxColors[token.Type]); } }
Testing on a 2KB document with a little over 100 tokens took 1-2 seconds to redraw after each key press; clearly not acceptable. Profiling showed that my tokenizer was an order of magnitude faster than the Format () function. So I tried some double buffering:
private void Render(string text) { XmlView.TextChanged -= this.XmlChanged; // create new doc offscreen var doc = new FlowDocument(); var range = new TextRange(doc.ContentStart, doc.ContentEnd); range.Text = text; if (VM.Pretty) { var tokens = tokenizer.Tokenize(text); Format(doc, tokens); } // copy to active buffer var stream = new MemoryStream(65536); range.Save(stream, DataFormats.XamlPackage); var activeRange = new TextRange(XmlView.Document.ContentStart, XmlView.Document.ContentEnd); activeRange.Load(stream, DataFormats.XamlPackage); XmlView.TextChanged += this.XmlChanged; }
Tests show that Format () is a little faster, rendering off-screen, but perceived performance is even worse now!
How to do it right?
source share