C#WPF为RichTextBox文本中的特定单词着色

时间:2019-02-21 15:14:17

标签: wpf richtextbox

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


以下代码: 首先,我用正则表达式搜索所有变量及其位置。(变量看起来像:<* variable *>)然后循环通过槽并逐一更改颜色,但是当我制作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:

article解决方案之后,我更改了代码。

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

它直接寻找看起来像<* word *>的单词。它可以找到所有单词,但是格式字符仍然有问题。

user8478480

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

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

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

2 个答案:

答案 0 :(得分:0)

我并不是说这是最迷人的方式,但是RichTextBox控件在标准WPF工具包中并不是很容易使用。因此,这是我已经完成您尝试完成的工作的方式。

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

P.S在考虑了这一点之后可能并不需要所有代码,因为我的实现也具有跳转到行的功能,因此为什么我将文档分成几行。祝你好运!

     //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 :(得分:0)

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

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

解决方案: 始终只更改第一场比赛。完成后,获取新的文本指针,这将使您在带颜色的单词之前返回文本。然后再次给您上色的单词(由于两次上色,它必须跳过)。然后,在您的彩色单词之后返回文本,该单词包含该行中的其他特殊单词。

示例: 我想给单词加上颜色:<* word *>。

要着色的文本:“这是<* test *> <*句子*>。”

  • 第一步:退回整行。正则表达式给出了2个匹配项 (<*测试*>,<*句子*>)。为第一个着色。

  • 第二步:回馈:“这是一个”。不做任何事

  • 第三步:回馈:“ <* test *>”。正则表达式给出1个匹配项(<* test *>)。 它,因为它已经着色了。
  • 第四步:返回:“”。不执行任何操作。
  • 第五步:回馈:“ <*句子*>”。正则表达式进行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);
        }
    }