WPF Richtextbox Application.Find文本跨越多次运行

时间:2014-03-06 16:05:24

标签: wpf richtextbox

我正在尝试为WPF richtextbox实现Application.Find命令。假设我正在寻找“专家”。听起来很容易。但是由于wpf的性质,如果“expert”中的每个其他字母都加粗,那么richtextbox包含e * x * p * e * r * t *这意味着存在六次运行。我有一个起始textPointer。我想弄清楚的是如何获得结束textPointer,以便我可以创建一个可以用来创建Selection的TextRange。

在此示例中,起始textpointer位于第一次运行中,结束textpointer应位于上次运行中。如果您知道运行中的运行和偏移量,是否有一种生成文本指针的简单方法?我尝试使用第一个textpointer的偏移量生成它,但是这不起作用,因为偏移量不在第一次运行中。

作为WPF richtextbox的相对新手,这个让我难过。我想这个问题已经解决并解决了。我确实找到了一个部分解决方案,但它只能在一次运行中工作,并没有解决多次运行情况。

2 个答案:

答案 0 :(得分:7)

我们的想法是找到第一个字符(IndexOf)的偏移量,然后在此索引处找到TextPointer(但只计算文本字符)。

public TextRange FindTextInRange(TextRange searchRange, string searchText)
{
    int offset = searchRange.Text.IndexOf(searchText, StringComparison.OrdinalIgnoreCase);
    if (offset < 0)
        return null;  // Not found

    var start = GetTextPositionAtOffset(searchRange.Start, offset);
    TextRange result = new TextRange(start, GetTextPositionAtOffset(start, searchText.Length));

    return result;
}

TextPointer GetTextPositionAtOffset(TextPointer position, int characterCount)
{
    while (position != null)
    {
        if (position.GetPointerContext(LogicalDirection.Forward) == TextPointerContext.Text)
        {
            int count = position.GetTextRunLength(LogicalDirection.Forward);
            if (characterCount <= count)
            {
                return position.GetPositionAtOffset(characterCount);
            }

            characterCount -= count;
        }

        TextPointer nextContextPosition = position.GetNextContextPosition(LogicalDirection.Forward);
        if (nextContextPosition == null)
            return position;

        position = nextContextPosition;
    }

    return position;
}

这是如何使用代码:

TextRange searchRange = new TextRange(richTextBox.Document.ContentStart, richTextBox.Document.ContentEnd);
TextRange foundRange = FindTextInRange(searchRange, "expert");
foundRange.ApplyPropertyValue(TextElement.ForegroundProperty, new SolidColorBrush(Colors.Red));

答案 1 :(得分:0)

在测试了 meziantou 的解决方案并使用我的测试数据获取错位的 textrange 之后,我通过在每个字符的基础上遍历文本范围来想出这个。我搜索了互联网,meziantou 的样本是我遇到的唯一一个甚至考虑多次运行的东西(你认为这应该很常见吧?)但它不适用于新的线条或 uielements。我在下面的解决方案确实如此,有关更多详细信息以及在何处处理内联元素,请参阅评论:

public static List<TextRange> FindStringRangesFromPosition(TextPointer position, string matchStr, bool isCaseSensitive = false) {
        var matchRangeList = new List<TextRange>();
        while (position != null) {
            var hlr = FindStringRangeFromPosition(position, matchStr, isCaseSensitive);
            if (hlr == null) {
                break;
            } else {
                matchRangeList.Add(hlr);
                position = hlr.End;
            }
        }
        return matchRangeList;
    }

    public static TextRange FindStringRangeFromPosition(TextPointer position, string matchStr, bool isCaseSensitive = false) {
        int curIdx = 0;
        TextPointer startPointer = null;
        StringComparison stringComparison = isCaseSensitive ? StringComparison.CurrentCulture : StringComparison.CurrentCultureIgnoreCase;
        while (position != null) {   
            if (position.GetPointerContext(LogicalDirection.Forward) != TextPointerContext.Text) {
                if(position.GetPointerContext(LogicalDirection.Forward) == TextPointerContext.EmbeddedElement) {
                    var inlineUIelement = position.Parent;
                    //handle inlineUIelement.Child contents here...
                }
                position = position.GetNextContextPosition(LogicalDirection.Forward);
                continue;
            }
            var runStr = position.GetTextInRun(LogicalDirection.Forward);
            if (string.IsNullOrEmpty(runStr)) {
                position = position.GetNextContextPosition(LogicalDirection.Forward);
                continue;
            }
            //only concerned with current character of match string
            int runIdx = runStr.IndexOf(matchStr[curIdx].ToString(), stringComparison);
            if (runIdx == -1) {
                //if no match found reset search
                curIdx = 0;
                if (startPointer == null) {
                    position = position.GetNextContextPosition(LogicalDirection.Forward);
                } else {
                    //when no match somewhere after first character reset search to the position AFTER beginning of last partial match
                    position = startPointer.GetPositionAtOffset(1, LogicalDirection.Forward);
                    startPointer = null;
                }
                continue;
            }
            if (curIdx == 0) {
                //beginning of range found at runIdx
                startPointer = position.GetPositionAtOffset(runIdx, LogicalDirection.Forward);
            }
            if (curIdx == matchStr.Length - 1) {
                //each character has been matched
                var endPointer = position.GetPositionAtOffset(runIdx, LogicalDirection.Forward);
                //for edge cases of repeating characters these loops ensure start is not early and last character isn't lost 
                if (isCaseSensitive) {
                    while (endPointer != null && !new TextRange(startPointer, endPointer).Text.Contains(matchStr)) {
                        endPointer = endPointer.GetPositionAtOffset(1, LogicalDirection.Forward);
                    }
                } else {
                    while (endPointer != null && !new TextRange(startPointer, endPointer).Text.ToLower().Contains(matchStr.ToLower())) {
                        endPointer = endPointer.GetPositionAtOffset(1, LogicalDirection.Forward);
                    }
                }
                if (endPointer == null) {
                    return null;
                }
                while (startPointer != null && new TextRange(startPointer, endPointer).Text.Length > matchStr.Length) {
                    startPointer = startPointer.GetPositionAtOffset(1, LogicalDirection.Forward);
                }
                if (startPointer == null) {
                    return null;
                }
                return new TextRange(startPointer, endPointer);
            } else {
                //prepare loop for next match character
                curIdx++;
                //iterate position one offset AFTER match offset
                position = position.GetPositionAtOffset(runIdx + 1, LogicalDirection.Forward);
            }
        }
        return null;
    }

用法:

List<TextRange> matchRangeList = FindStringRangesFromPosition(rtb.Document.ContentStart,"expert",false);
相关问题