我正在尝试为WPF richtextbox实现Application.Find命令。假设我正在寻找“专家”。听起来很容易。但是由于wpf的性质,如果“expert”中的每个其他字母都加粗,那么richtextbox包含e * x * p * e * r * t *这意味着存在六次运行。我有一个起始textPointer。我想弄清楚的是如何获得结束textPointer,以便我可以创建一个可以用来创建Selection的TextRange。
在此示例中,起始textpointer位于第一次运行中,结束textpointer应位于上次运行中。如果您知道运行中的运行和偏移量,是否有一种生成文本指针的简单方法?我尝试使用第一个textpointer的偏移量生成它,但是这不起作用,因为偏移量不在第一次运行中。
作为WPF richtextbox的相对新手,这个让我难过。我想这个问题已经解决并解决了。我确实找到了一个部分解决方案,但它只能在一次运行中工作,并没有解决多次运行情况。
答案 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);