TextRange GetPositionAtOffset 的行为不符合预期

本文关键字:不符合 GetPositionAtOffset TextRange | 更新日期: 2023-09-27 18:31:27

我正在尝试使用WPF RichTextEditor实现基本的语法突出显示。为此,我想以不同的颜色显示{}个格罗普斯。

以下是应该将RichTextBox的内容拆分为不同组的代码:

    List<Tag> SplitIntoParts(TextRange textRange, int level)
    {
        if (textRange==null||textRange.IsEmpty)
        {
            return new List<Tag>();
        }
        string text = textRange.Text;
        if (text.Length==0)
        {
            return new List<Tag>();
        }
        int startPos=-1, counter = 0;
        List<Tag> result=new List<Tag>();
        for (int i = 0; i < text.Length; i++)
        {
            if (text[i]=='{')
            {
                if (counter==0)
                {
                    startPos = i;
                }
                counter++;
            }
            if (text[i]=='}')
            {
                if (counter==1)
                {
                    Tag t = new Tag()
                                {
                                    StartPosition = textRange.Start.GetPositionAtOffset(startPos), 
                                    EndPosition = textRange.Start.GetPositionAtOffset(i+1), 
                                    Level = level,
                                    Word = text.Substring(startPos,i+1-startPos)
                                };
                    result.Add(t);
                    var tr=new TextRange(textRange.Start.GetPositionAtOffset(startPos + 1), textRange.Start.GetPositionAtOffset(i));
                    result.AddRange(SplitIntoParts(tr, level + 1));
                }
                counter--;
            }
        }
        if (counter>0)//some open branches still left
        {
            var i = text.Length;
            Tag t = new Tag()
            {
                StartPosition = textRange.Start.GetPositionAtOffset(startPos),
                EndPosition = textRange.End,
                Level = level,
                Word = text.Substring(startPos, i - startPos)
            };
            result.Add(t);
            result.AddRange(SplitIntoParts(new TextRange(textRange.Start.GetPositionAtOffset(startPos + 1), textRange.Start.GetPositionAtOffset(i - 1)), level + 1));
        }
        return result;
    }

在此代码中,我发现textRange.Start.GetPositionAtOffset(startPos + 1)的行为很奇怪:

假设代码找到了以下组:

{test|try}

并使用以下代码选择了它:

var t=new Tag()
                                {
                                    StartPosition = textRange.Start.GetPositionAtOffset(startPos), 
                                    EndPosition = textRange.Start.GetPositionAtOffset(i+1), 
                                    Level = level,
                                    Word = text.Substring(startPos,i+1-startPos)
                                };

(例如 t.Word=='{test|try}')

当我尝试通过传递递归地做同样的事情时

var tr=new TextRange(textRange.Start.GetPositionAtOffset(startPos + 1), textRange.Start.GetPositionAtOffset(i));
result.AddRange(SplitIntoParts(tr, level + 1));

而不是"test|try",tr。文本 =="{测试"

为什么会出现这种行为,我应该如何处理它?

TextRange GetPositionAtOffset 的行为不符合预期

GetPositionAtOffset不只计算(可见)字符。幸运的是,我最近遇到了同样的问题,所以我创建了一个方法,该方法在指定的偏移量(仅计算可见字符的偏移量)处获取TextPointer。首先,它可能看起来有点复杂,但实际上并非如此:-)。

作为一个参数,它需要内联(来自富文本框,如RichTextBox.Document.Blocks.FirstBlock.Inlines,这分别只获取 rtb 中第一段的内联,如果有的话......);第二个参数是偏移量本身。

建议给出第三个参数,即指示内容开始的TextPointer。如果指定了内联,则从第一个内联确定起始位置,但在没有内联的情况下,将引发异常,为避免这种情况,请将内容开始参数设置为 RichTextBox.Document.ContentStart 。方法如下:

    /// <summary>
    /// Returns the position of the specified offset in the text specified by the inlines.
    /// </summary>
    /// <param name="inlines">The inlines which specifies the text.</param>
    /// <param name="offset">The offset within the text to get the position of.</param>
    /// <param name="contentStartPosition">The position where the content starts. If null, the position before the start of the first inline will be used. If null and there are no inlines, an exception is thrown.</param>
    /// <returns>A <see cref="TextPointer"/> indicating the position of the specified offset.</returns>
    public static TextPointer GetPositionAtOffset(this InlineCollection inlines, int offset, TextPointer contentStartPosition = null)
    {
        if (inlines == null)
            throw new ArgumentNullException(nameof(inlines));
        if (!inlines.Any() && contentStartPosition == null)//if no inlines, can't determine start of content
            throw new ArgumentException("A content start position has to be specified if the inlines collection is empty.", nameof(contentStartPosition));
        if (contentStartPosition == null)
            contentStartPosition = inlines.First().ContentStart.DocumentStart;//if no content start specified, gets it
        int offsetWithInlineBorders = 0;//collects the value of offset (with inline borders)
        foreach (var inline in inlines)
        {
            int inlineLength = (inline as Run)?.Text.Length ?? (inline is LineBreak ? 1 : 0);//gets the length of the inline (length of a Run is the lengts of its text, length of a LineBreak is 1, other types are ignored)
            if (inlineLength < offset)//if position specified by the offset is beyond this inline...
                offsetWithInlineBorders += inlineLength + 2;//...then the whole length is added with the two borders
            else if (inlineLength == offset)//if position specified by the offset is at the end of this inline...
                offsetWithInlineBorders += inlineLength + 1;//...then the whole length is added with only the opening border
            else //inlineLength > value, if the position specified by the offset is within this inline
            {
                offsetWithInlineBorders += offset + 1;//...then adds the remaining length (the offset itself), plus the opening border
                break;//the inlines beyond are not needed
            }
            offset -= inlineLength;//substracts the added inline length
        }
        return contentStartPosition.GetPositionAtOffset(
            Math.Min(Math.Max(offsetWithInlineBorders, 0), contentStartPosition.GetOffsetToPosition(contentStartPosition.DocumentEnd)));//if the value is not within the boundaries of the text, returns the start or the end of the text
    }

祝你好运

GetPositionAtOffset计算符号,这可能比文本插入位置更多。请参阅 MSDN:

返回一个文本指针,指向由指定 以符号为单位,从当前文本指针的开头偏移。