【问题标题】:C# WPF colorize specific words in RichTextBox textC# WPF 为 RichTextBox 文本中的特定单词着色
【发布时间】:2019-02-21 15:14:16
【问题描述】:

我对FlowDocument有一个误解,请帮我看清楚。 我正在开发一个源代码编辑器,用户可以在其中添加一些特殊变量,然后程序会查找这些变量。对于这个编辑器,我使用的是 RichTextBox(RTB)。我想为这些变量使用颜色。当用户向文本添加新变量时,添加颜色不是问题。但是当用户首先打开一个已经有一些变量的源代码时,我必须深入研究整个文本并对变量进行着色。


下面的代码: 首先,我使用正则表达式搜索所有变量及其位置。(变量看起来像:)然后循环槽并一一更改颜色,但是当我制作 TextRange 时,GetPositionAtOffset 会返回错误的值.我知道这是因为 GetPositionAtOffset 也计算了特殊格式字符。 问题是,我该如何解决这个问题?

private void ColorizeAllVariable(TextRange TR_Input)
    {
        Regex regex = new Regex(@"(<\*.[^<\*>]*\*>)");
        MatchCollection matches = regex.Matches(TR_Input.Text);
        NoRTBChangeEvent = true;
        for (int i = 0; i < matches.Count; i++)
        {
            TextRange TR_Temp = new TextRange(TR_Input.Start.GetPositionAtOffset(matches[i].Index), TR_Input.Start.GetPositionAtOffset(matches[i].Index + matches[i].Length));
            TR_Temp.ApplyPropertyValue(TextElement.ForegroundProperty, Brushes.DodgerBlue);
        }
        NoRTBChangeEvent = false;
    }

更新 1:

按照user8478480 解决方案,我更改了我的代码。

private void ColorizeAllVariable(RichTextBox richTextBox)
    {
        IEnumerable<TextRange> WordRanges = GetAllWordRanges(richTextBox.Document, @"(<\*.[^<\*>]*\*>)");

        foreach (TextRange WordRange in WordRanges)
        {
            WordRange.ApplyPropertyValue(TextElement.ForegroundProperty, Brushes.DodgerBlue);
        }
    }

private static IEnumerable<TextRange> GetAllWordRanges(FlowDocument document, string pattern)
    {
        TextPointer pointer = document.ContentStart;
        while (pointer != null)
        {
            if (pointer.GetPointerContext(LogicalDirection.Forward) == TextPointerContext.Text)
            {
                string textRun = pointer.GetTextInRun(LogicalDirection.Forward);
                MatchCollection matches = Regex.Matches(textRun, pattern);
                foreach (Match match in matches)
                {
                    int startIndex = match.Index;
                    int length = match.Length;
                    TextPointer start = pointer.GetPositionAtOffset(startIndex);
                    TextPointer end = start.GetPositionAtOffset(length);
                    yield return new TextRange(start, end);
                }
            }
            pointer = pointer.GetNextContextPosition(LogicalDirection.Forward);
        }
    }

它直接寻找看起来像的单词。它找到了所有单词,但格式字符仍然存在问题。

This is the result. The second word in the line has wrong coloring position

This is how the line looks like, when it search for the word

This is another trying

我看到了问题,当我添加颜色属性时,它会移动数据,但我的匹配包含着色之前的位置。

看起来很简单,如果我在一行中有多个匹配项,我总是将位置移动一个常数值。但是格式化字符看起来并不总是相同的长度。正如您在第二次尝试中看到的那样,第一个可变颜色是正确的。比第二个有 5 个字符移位,第三个变量也有 5 个字符移位,第四个变量有 9 个字符移位,第五个变量有 13 个字符移位,第六个......(我不知道这里发生了什么) ,最后第七个变量的颜色位置也很好。

【问题讨论】:

    标签: wpf richtextbox


    【解决方案1】:

    我也找到了问题和解决方案。

    问题: 当正则表达式在一行中找到所有匹配项时,没有颜色格式。但是当我将颜色格式添加到第一个匹配项时,它会移动文本,但正则表达式匹配结果仍然是旧位置。

    解决方案: 始终只更改第一个匹配项。完成后,获取新的文本指针,它将在您的彩色单词之前返回文本。然后再次返回你着色的单词(它必须跳过,因为两次着色)。而不是在您的彩色单词之后返回文本,包含该行中其他特殊单词的内容。

    示例: 我想给单词上色:.

    要着色的文本:“这是一个 。”

    • 第一步:归还整行。正则表达式给出 2 个匹配项 (,)。给第一个上色。

    • 第二步:返回:“这是一个”。什么都不做

    • 第三步:返回:“”。正则表达式给出 1 个匹配项 ().Skip 它,因为它已经着色了。
    • 第四步:返回:“”。什么都不做。
    • 第五步:返回:“”。正则表达式给出 1 个匹配项 ()。着色。
    • 第六步:返回:“”。正则表达式给出 1 个匹配项 ()。跳过它,因为它已经上色了。
    • 结束

      {
          TextPointer pointer = document.ContentStart;
          bool Skip = false;
          string textRun = "";
      
          while (pointer != null)
          {
              if (pointer.GetPointerContext(LogicalDirection.Forward) == TextPointerContext.Text)
              {
                  do
                  {
                      if (!Skip)
                      {
                          textRun = pointer.GetTextInRun(LogicalDirection.Forward);
                          MatchCollection Matches = Regex.Matches(textRun, pattern);
                          if (Matches.Count > 0)
                          {
                              Skip = true;
                              int startIndex = Matches[0].Index;
                              int length = Matches[0].Length;
                              TextPointer start = pointer.GetPositionAtOffset(startIndex);
                              TextPointer end = start.GetPositionAtOffset(length);
                              yield return new TextRange(start, end);
                          }
                      }
                      else
                      {
                          pointer = pointer.GetNextContextPosition(LogicalDirection.Forward);
                          if (pointer.GetPointerContext(LogicalDirection.Forward) == TextPointerContext.Text)
                          {
                              textRun = pointer.GetTextInRun(LogicalDirection.Forward);
                              if(Regex.IsMatch(textRun,pattern))
                              {
                                  Skip = false;
                              }
                          }
                      }
                  } while (Skip);
              }
              pointer = pointer.GetNextContextPosition(LogicalDirection.Forward);
          }
      }
      

    【讨论】:

      【解决方案2】:

      我并不是说这是最迷人的方式,但RichTextBox 控件在标准WPF 工具包中并不是很容易使用。所以这是我以前尝试完成的一种方法。

      本质上,这种拆分会获取您的原始内容,将其拆分为流文档元素,然后将文档中的每个单词作为文本范围进行迭代。然后,如果每个单词符合条件,它就会将格式应用到每个单词,如Foreach 中所述。希望这会有所帮助。

      PS 在考虑之后可能不需要所有代码,因为我的实现也有跳转到行的功能,因此我将文档分成几行。祝你好运!

           //new doc.
           var doc = new FlowDocument();
      
           //loop all lines from text.(split on \r\n)
           string[] lines = RichTextBoxExtraControl.Text.Split(new string[] { "\r\n" }, StringSplitOptions.None);
           for (int i = 0; i < lines.Length; i++)
           {
      
              //make new paragraph
              var run = new Run(lines[i]);
              var par = new Paragraph(run);
              par.LineHeight = 1;
              doc.Blocks.Add(par);
          }
      
           //Searches a list of all words to highlight in place the words below
           IEnumerable<TextRange> wordRanges = GetAllWordRanges(doc);
           foreach (TextRange wordRange in wordRanges)
           {
              if (wordRange.Text == ">WORD YOU WANT TO HIGHLIGHT<")
              {
                 wordRange.ApplyPropertyValue(TextElement.BackgroundProperty, Brushes.Red); //Effect to apply.
              }
           }
      
           //Set document.
           RichTextBox1.Document = doc;      
        }
      

      使用这个方法Highlighting keywords in a richtextbox in WPF

        public static IEnumerable<TextRange> GetAllWordRanges(FlowDocument document)
        {
           string pattern = @"[^\W\d](\w|[-']{1,2}(?=\w))*";
           TextPointer pointer = document.ContentStart;
           while (pointer != null)
           {
              if (pointer.GetPointerContext(LogicalDirection.Forward) == TextPointerContext.Text)
              {
                 string textRun = pointer.GetTextInRun(LogicalDirection.Forward);
                 MatchCollection matches = Regex.Matches(textRun, pattern);
                 foreach (Match match in matches)
                 {
                    int startIndex = match.Index;
                    int length = match.Length;
                    TextPointer start = pointer.GetPositionAtOffset(startIndex);
                    TextPointer end = start.GetPositionAtOffset(length);
                    yield return new TextRange(start, end);
                 }
              }
              pointer = pointer.GetNextContextPosition(LogicalDirection.Forward);
           }
        }
      

      【讨论】:

      • 感谢您的回答,我更改了代码,但格式字符仍然存在问题,请观看我上面的帖子更新 1。
      • @Robotsyte 尝试复制一些数据,将其放入正则表达式测试器并检查您的模式是否正确。这可能是一个可能的原因?同样,该模式用于拆分单词。 regex101.com
      • 正则表达式模式是正确的,它找到了正确的变量。 check this photo
      • 这可行,但由于某种原因非常慢。如果文本超过几行,不知道为什么加载需要几秒钟。
      猜你喜欢
      • 2014-03-25
      • 2018-08-26
      • 1970-01-01
      • 1970-01-01
      • 2011-04-15
      • 2014-05-15
      • 1970-01-01
      • 1970-01-01
      • 2015-05-16
      相关资源
      最近更新 更多