RichTextBox语法高亮显示

4

我正在使用这段代码,它用于在RichTextBox中实现语法高亮。我特别关注ProcessLine()OnTextChanged()这两个函数,并对它们进行了修改:

protected override void OnTextChanged(EventArgs e)
{
    // Calculate stuff here.
    m_nContentLength = this.TextLength;

    int nCurrentSelectionStart = SelectionStart;
    int nCurrentSelectionLength = SelectionLength;

    m_bPaint = false;

    // Find the start of the current line.
    m_nLineStart = nCurrentSelectionStart;
    while ((m_nLineStart > 0) && (Text[m_nLineStart - 1] != '\n'))
        m_nLineStart--;
    // Find the end of the current line.
    m_nLineEnd = nCurrentSelectionStart;
    while ((m_nLineEnd < Text.Length) && (Text[m_nLineEnd] != '\n'))
        m_nLineEnd++;
    // Calculate the length of the line.
    m_nLineLength = m_nLineEnd - m_nLineStart;
    // Get the current line.
    m_strLine = Text.Substring(m_nLineStart, m_nLineLength);

    // Process this line.
    ProcessLine();

    m_bPaint = true;
}

// Process a line.
private void ProcessLine()
{
    // Save the position and make the whole line black
    int nPosition = SelectionStart;
    SelectionStart = m_nLineStart;
    SelectionLength = m_nLineLength;
    SelectionColor = Color.Black;

    /*// Process the keywords
    ProcessRegex(m_strKeywords, Settings.KeywordColor);
    // Process numbers
    if(Settings.EnableIntegers)
        ProcessRegex("\\b(?:[0-9]*\\.)?[0-9]+\\b", Settings.IntegerColor);
    // Process strings
    if(Settings.EnableStrings)
        ProcessRegex("\"[^\"\\\\\\r\\n]*(?:\\\\.[^\"\\\\\\r\\n]*)*\"", Settings.StringColor);
    // Process comments
    if(Settings.EnableComments && !string.IsNullOrEmpty(Settings.Comment))
        ProcessRegex(Settings.Comment + ".*$", Settings.CommentColor);*/

    SelectionStart = nPosition;
    SelectionLength = 0;
    SelectionColor = Color.Red;

    m_nCurSelection = nPosition;
}
  • 我的第一个问题是,在进入OnTextChanged()中的ProcessLine()时,m_strLine末尾是否总是有一个换行符?最小值或m_strLine的值为"\n",而最大值为"any#ofchars+\n"吗?

  • 然后,我想确认一下,如果SelectionLength为零,则SelectionStart是我光标的位置,如果 SelectionLength 大于零,则我的光标在SelectStart + SelectionLength处?

  • 我试图修改此代码以着色许多不同语法表达式,并计划逐个字符地处理每行。当粘贴或加载包含20k+行的文件时,这可能会如何处理?


这是一种非常基本的方法,无法处理较大的代码片段。它会非常缓慢,并且会出现很多闪烁和问题。最好从一个更好的基础开始,而不是使用开箱即用的 RTF 控件! - banging
我是否最好尝试像他们在这里做的那样对每一行进行正则表达式处理? - AnotherUser
同时,只有在加载文件时我才需要逐行处理。另一方面,在编辑文件时,我每次只需要检查一行,但仍然需要重绘,但仅重绘用户在文本区域中可见的部分。这样真的会像大家想象的那样慢吗? - AnotherUser
如果你想自己做,Costin Boldisor在这里有一篇博客文章 https://blogs.msdn.microsoft.com/cobold/2011/01/31/xml-highlight-in-richtextbox/ - Vinod Srivastav
4个回答

7
我现在能够建议你使用更稳定、更强大且更少出错的东西,例如Scintilla for .NET和Color Code。这些控件是免费且开源的。试一试: ScintillaNET ColorCode - 为.NET提供语法高亮/着色 RichTextBox在处理大文本时效率极低。即使你获得了一些不错的高亮显示,性能问题也很快会出现。

一个很好的回答,但是...有没有任何方法可以使用RichTextBox来实现这个功能?如果不行,你能指导我创建自己的TextBox控件吗?谢谢 :) - Momoro

1

这将会导致可扩展性非常糟糕。如果你的目标只是一个功能正常的应用程序,那么你应该按照DelegateX的建议去做;如果你想学习如何做到这一点,那么首先要想办法降低完成工作的量。为此,以下是一些通用指针:

仅突出显示窗口内的文本将是一个巨大的改进,没有任何视觉副作用 - 将文本按功能、方法、类等分块,并仅突出显示可见块,甚至遮挡部分,以避免偏移起始位置影响突出显示的问题。如果你不这样做,就会遇到第一行渲染在if或括号块的中间的情况,结果会导致语法树不平衡。

使用RichTextBox控件仍然无法处理20k行,但几千行应该很快。


MVS 08中没有任何东西可以处理20k+行文本吗?我只是不想从头开始创建这样的东西。可能只显示文本块,将其余部分保存在文本文件中,并在需要时从文件中提取它? - AnotherUser
我不知道有什么东西可以直接使用,但是DelegateX的任何建议都应该很容易地解决问题。问题在于处理2万行文本本质上是复杂的:更改一行的长度会影响其后的每一行。我相信.NET在幕后将整个块呈现为一个巨大的纹理 - 尽管我可能错了 - 这将无论你做什么都会变得很慢,并意味着你受到带宽限制而不是受到CPU限制。 - Winfield Trail
最容易对系统的方法是将每个文档分成大约500行的块,并根据需要从内存中流式传输到磁盘。但这会使您的滚动条变得奇怪,因此您需要处理它。修复滚动条将意味着扩展那些类以处理通过不存在的空间滚动。这就是为什么找到一个处理大文件的文本编辑器如此困难的原因之一 - 这也是为什么大多数现代开发范例涉及将代码分散在几十个或几百个单独的文件中的原因之一。 - Winfield Trail

0
修复版本 - 处理 JSON 作为内部文本并提取元素更好
        public static void HighlightXml(this RichTextBox richTextBox)
        {
            // Collect Text-Box Information
            var textRange = new TextRange(richTextBox.Document.ContentStart, richTextBox.Document.ContentEnd).Text;
            XmlDocument xmlDocument = new XmlDocument();
            try
            {
                xmlDocument.LoadXml(textRange.Trim());
            }
            catch
            {
                return;
            }
            var documentLines = xmlDocument.OuterXml.Split(new[] { Environment.NewLine }, StringSplitOptions.None);

            // Get the Longest Line Length
            int? maxVal = null;
            for (int i = 0; i < documentLines.Length; i++)
            {
                int thisNum = documentLines[i].Length;
                if (!maxVal.HasValue || thisNum > maxVal.Value) { maxVal = thisNum; }
            }

            // Set Text-Box Width & Clear the Current Content
            if (maxVal != null) richTextBox.Document.PageWidth = (double)maxVal * 10;
            richTextBox.Document.Blocks.Clear();

            #region *** Process Lines ***
            foreach (var documentLine in documentLines)
            {
                // Parse XML Node Components
                var indentSpace = Regex.Match(documentLine, @"\s+").Value;
                var xmlTags = Regex.Matches(documentLine, @"(?<=<)[^>\s+]*");
                if (documentLine.Contains("<!--")) xmlTags = Regex.Matches(documentLine, @"(<[^/].+?>)");
                var nodeAttributes = Regex.Matches(documentLine, @"(?<=\s)[^><:\s]*=*(?=[>,\s])");

                // Process XML Node
                var nodeAttributesCollection = new List<Run>();
                if (nodeAttributes.Count > 0)
                {
                    for (int i = 0; i < nodeAttributes.Count; i++)
                    {
                        if (!(nodeAttributes[i].Value.Length < 2) && !(documentLine.Contains("<!--")))
                        {
                            var attributeName = $"{Regex.Match(nodeAttributes[i].Value, @"(.+?=)").Value}";
                            if (i == 0) attributeName = $" {Regex.Match(nodeAttributes[i].Value, @"(.+?=)").Value}";
                            var attributeValue = $"{Regex.Match(nodeAttributes[i].Value, @"(?<=(.+?=))"".+?""").Value} ";

                            if (i == nodeAttributes.Count - 1) attributeValue = attributeValue.Trim();
                            nodeAttributesCollection.Add(new Run { Foreground = new SolidColorBrush(Colors.Green), Text = $"{attributeName}" });
                            nodeAttributesCollection.Add(new Run { Foreground = new SolidColorBrush(Colors.Brown), Text = $"{attributeValue}" });
                        }
                    }
                }

                // Initialize IndentSpace
                Run run = null;
                if (indentSpace.Length > 1) run = new Run { Text = indentSpace };

                // Initialize Open Tag
                var tagText = xmlTags[0].Value;//.Substring(1, xmlTags[0].Value.Length - 2);
                var tagTextBrush = new SolidColorBrush(Colors.Blue);
                var tagBorderBruh = new SolidColorBrush(Colors.Red);
                if (tagText.StartsWith("!--"))
                {
                    tagTextBrush = new SolidColorBrush(Colors.DarkSlateGray);
                    tagBorderBruh = new SolidColorBrush(Colors.DarkSlateGray);
                }
                var openTag = new Run
                {
                    Foreground = tagTextBrush,
                    Text = tagText
                };

                // Initialize Content Tag
                var content = new Run
                {
                    Foreground = new SolidColorBrush(Colors.Black),
                };

                // Initialize Paragraph
                var paragraph = new Paragraph();
                paragraph.Margin = new Thickness(0);
                if (run != null) paragraph.Inlines.Add(run); // Add indent space if exist

                // Process Open Tag
                paragraph.Inlines.Add(new Run { Foreground = tagBorderBruh, Text = "<" });
                paragraph.Inlines.Add(openTag);

                // Process Open Tag Attributes
                if (nodeAttributesCollection.Count > 0)
                {
                    nodeAttributesCollection.ForEach(attribute => { paragraph.Inlines.Add(attribute); });
                    nodeAttributesCollection.Clear();
                }
                paragraph.Inlines.Add(new Run { Foreground = tagBorderBruh, Text = ">" });

                // Process Closing Tag
                if (xmlTags.Count > 1)
                {
                    Run closingTag = new Run();
                    content.Text = documentLine.Replace($"<{xmlTags[0].Value}>", "").Replace($"<{xmlTags[1].Value}>", "").Trim();
                    closingTag = new Run
                    {
                        Foreground = new SolidColorBrush(Colors.Blue),
                        Text = xmlTags[1].Value.Substring(1, xmlTags[1].Value.Length - 1)
                    };
                    paragraph.Inlines.Add(content);

                    paragraph.Inlines.Add(new Run { Foreground = new SolidColorBrush(Colors.Red), Text = "<" });
                    paragraph.Inlines.Add(closingTag);
                    paragraph.Inlines.Add(new Run { Foreground = new SolidColorBrush(Colors.Red), Text = ">" });
                }
                richTextBox.Document.Blocks.Add(paragraph);
            }
            #endregion
        }

0

面对同样的问题,找不到“5分钟即可上手”的解决方案,我开发了自己的RichTextBox扩展来突出显示XML。

由于时间紧迫,我很快就开发出来了,但没有时间进行修订 - 所以请随意完善它。

只需复制并粘贴扩展代码以与您的RichTextBox一起使用,或者复制整个应用程序代码,包括同步和异步使用。

扩展方法

// Use for asynchronous highlight
public delegate void VoidActionOnRichTextBox(RichTextBox richTextBox);

// Extension Class
public static class RichTextBoxExtensions
{
    public static void HighlightXml(this RichTextBox richTextBox)
    {
        new StandardHighlight().HighlightXml(richTextBox);
    }
    public async static void HighlightXmlAsync(this RichTextBox richTextBox)
    {
        var helper = new StandardHighlight();
        var win = new MainWindow();
        await Task.Factory.StartNew(() =>
        {
            richTextBox.Dispatcher.BeginInvoke(new VoidActionOnRichTextBox(helper.HighlightXml), richTextBox);
        });
    }        
}

// You can extent it with more highlight methods
public class StandardHighlight
{        
    public void HighlightXml(RichTextBox richTextBox)
    {
        // Collect Text-Box Information
        var textRange = new TextRange(richTextBox.Document.ContentStart, richTextBox.Document.ContentEnd).Text;
        XDocument xDocument;
        try
        {
            xDocument = XDocument.Parse(textRange);
        }
        catch
        {
            return;
        }
        var documentLines = xDocument.ToString().Split(new[] { Environment.NewLine }, StringSplitOptions.None);

        // Get the Longest Line Length
        int? maxVal = null;
        for (int i = 0; i < documentLines.Length; i++)
        {
            int thisNum = documentLines[i].Length;
            if (!maxVal.HasValue || thisNum > maxVal.Value) { maxVal = thisNum; }
        }

        // Set Text-Box Width & Clear the Current Content
        if (maxVal != null) richTextBox.Document.PageWidth = (double)maxVal * 5.5;
        richTextBox.Document.Blocks.Clear();

        #region *** Process Lines ***
        foreach (var documentLine in documentLines)
        {
            // Parse XML Node Components
            var indentSpace = Regex.Match(documentLine, @"\s+").Value;
            var xmlTags = Regex.Matches(documentLine, @"(<[^/].+?)(?=[\s])|(<[^/].+?>)|(</.+?>)");
            if (documentLine.Contains("<!--")) xmlTags = Regex.Matches(documentLine, @"(<[^/].+?>)"); // Parse comments
            var nodeAttributes = Regex.Matches(documentLine, @"(?<=\s)(.+?)(?=\s)");

            // Process XML Node
            var nodeAttributesCollection = new List<Run>();
            if (nodeAttributes.Count > 0)
            {
                for (int i = 0; i < nodeAttributes.Count; i++)
                {
                    if (!(nodeAttributes[i].Value.Length < 2) && !(documentLine.Contains("<!--")))
                    {
                        var attributeName = $"{Regex.Match(nodeAttributes[i].Value, @"(.+?=)").Value}";
                        if (i == 0) attributeName = $" {Regex.Match(nodeAttributes[i].Value, @"(.+?=)").Value}";
                        var attributeValue = $"{Regex.Match(nodeAttributes[i].Value, @"(?<=(.+?=))"".+?""").Value} ";

                        if (i == nodeAttributes.Count - 1) attributeValue = attributeValue.Trim();
                        nodeAttributesCollection.Add(new Run { Foreground = new SolidColorBrush(Colors.Green), Text = $"{attributeName}" });
                        nodeAttributesCollection.Add(new Run { Foreground = new SolidColorBrush(Colors.Brown), Text = $"{attributeValue}" });
                    }
                }
            }

            // Initialize IndentSpace
            Run run = null;
            if (indentSpace.Length > 1) run = new Run { Text = indentSpace };

            // Initialize Open Tag
            var tagText = xmlTags[0].Value.Substring(1, xmlTags[0].Value.Length - 2);
            var tagTextBrush = new SolidColorBrush(Colors.Blue);
            var tagBorderBruh = new SolidColorBrush(Colors.Red);
            if (tagText.StartsWith("!--"))
            {
                tagTextBrush = new SolidColorBrush(Colors.DarkSlateGray);
                tagBorderBruh = new SolidColorBrush(Colors.DarkSlateGray);
            }
            var openTag = new Run
            {
                Foreground = tagTextBrush,
                Text = tagText
            };

            // Initialize Content Tag
            var content = new Run
            {
                Foreground = new SolidColorBrush(Colors.Black),
            };

            // Initialize Paragraph
            var paragraph = new Paragraph();
            paragraph.Margin = new Thickness(0);
            if (run != null) paragraph.Inlines.Add(run); // Add indent space if exist

            // Process Open Tag
            paragraph.Inlines.Add(new Run { Foreground = tagBorderBruh, Text = "<" });
            paragraph.Inlines.Add(openTag);

            // Process Open Tag Attributes
            if (nodeAttributesCollection.Count > 0)
            {
                nodeAttributesCollection.ForEach(attribute => { paragraph.Inlines.Add(attribute); });
                nodeAttributesCollection.Clear();
            }
            paragraph.Inlines.Add(new Run { Foreground = tagBorderBruh, Text = ">" });

            // Process Closing Tag
            if (xmlTags.Count > 1)
            {
                Run closingTag = new Run();
                content.Text = documentLine.Replace(xmlTags[0].Value, "").Replace(xmlTags[1].Value, "").Trim();
                closingTag = new Run
                {
                    Foreground = new SolidColorBrush(Colors.Blue),
                    Text = xmlTags[1].Value.Substring(1, xmlTags[1].Value.Length - 2)
                };
                paragraph.Inlines.Add(content);

                paragraph.Inlines.Add(new Run { Foreground = new SolidColorBrush(Colors.Red), Text = "<" });
                paragraph.Inlines.Add(closingTag);
                paragraph.Inlines.Add(new Run { Foreground = new SolidColorBrush(Colors.Red), Text = ">" });
            }
            richTextBox.Document.Blocks.Add(paragraph);
        }
        #endregion
    }
}

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