WPF:快速将格式应用于RichTextBox的方法

4

我将尝试为WPF RichTextBox显示基本语法高亮。虽然它基本上能够工作,但渲染性能非常差。

首先,我尝试了以下代码:

/// <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]);
    }
}

对于一个仅含有100多个标记的2KB文档进行测试,每次按键后重绘需要1-2秒钟,这显然是无法接受的。经过分析,我发现我的词法分析器比Format()函数快了几个数量级。因此,我尝试了一些双缓冲技术:

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;     
}

基准测试表明,将Format()渲染到屏幕外会稍微快一些,但现在感知性能更差了!

这该怎么办?


嗨,Richard,你解决了这个问题吗?我在使用WPF RichTextBox时遇到了类似的问题。最好的问候, Alan - Alan Spark
我的第三次尝试大致是这样的:(1)创建一个长时间运行的后台线程,管理其自己的文档XAML表示(2)在按键时,将其发布到另一个线程正在观察的队列上(3)在出队时,手动操作XAML文档(即使用字符串分析,而不是FlowDocument)(4)每当您达到一个好的停止点,就将修改后的XAML发布回UI线程并执行TextRange.Load()。 这样表现得更好,但仍然不太理想。 如果我仍然关心这个项目,我会在反编译器中查看VS是如何完成它的 :) - Richard Berg
1个回答

1
我建议尽可能减少方法/循环中的对象实例化,并传递引用。你每次按键都会在循环中调用new很多次。

很遗憾,TextRange类上的Start和End属性是只读的。在每次迭代中表示正确范围的唯一方法是创建新的范围。 - Richard Berg

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接