Выбор диапазона текста в WPF RichTextBox (FlowDocument) Программно

У меня есть этот WPF RichTextBox, и я хочу программным образом выбрать заданный диапазон букв/слов и выделить его. Я пробовал это, но это не работает, возможно, потому, что я не учитываю некоторые скрытые теги FlowDocument или аналогичные. Например, я хочу выбрать буквы 3-8, но выбирается 2-6):

var start = MyRichTextBox.Document.ContentStart;
var startPos = start.GetPositionAtOffset(3);
var endPos = start.GetPositionAtOffset(8);
var textRange = new TextRange(startPos,endPos);
textRange.ApplyPropertyValue(TextElement.ForegroundProperty,
    new SolidColorBrush(Colors.Blue));
textRange.ApplyPropertyValue(TextElement.FontWeightProperty, 
    FontWeights.Bold);

Я понял, что обработка RichTextBox немного сложнее, чем я думал:)

Обновление: я получил несколько ответов на форумах MSDN: Этот поток, где "dekurver" seid:

Сдвиги, которые вы указываете, не являются смещения символов, но смещения символов. Что вам нужно сделать, так это получить TextPointer, который, как вы знаете, смежен в текст, то вы можете добавить символ Смещения.

И "LesterLobo" сказал:

вам нужно будет пройти через абзацев и строк, чтобы найти Затем, а затем их смещения в цикле подать заявку на все выступления конкретный текст. обратите внимание, что при редактировании ваш текст будет перемещаться, но ваш выделить не будет связанных со смещением не текст. Однако вы могли создать пользовательский запуск и это...

По-прежнему ЛЮБОВЬ, чтобы увидеть пример кода для этого, если кто-то знает свой путь вокруг FlowDocuments...

EDIT У меня есть версия кода Kratz VB, она выглядит так:

private static TextPointer GetPoint(TextPointer start, int x)
{
    var ret = start;
    var i = 0;
    while (i < x && ret != null)
    {
        if (ret.GetPointerContext(LogicalDirection.Backward) == 
TextPointerContext.Text ||
            ret.GetPointerContext(LogicalDirection.Backward) == 
TextPointerContext.None)
            i++;
        if (ret.GetPositionAtOffset(1, 
LogicalDirection.Forward) == null)
            return ret;
        ret = ret.GetPositionAtOffset(1, 
LogicalDirection.Forward);
    }
    return ret;
}

И я использую его следующим образом:

Colorize(item.Offset, item.Text.Length, Colors.Blue);

private void Colorize(int offset, int length, Color color)
{
    var textRange = MyRichTextBox.Selection;
    var start = MyRichTextBox.Document.ContentStart;
    var startPos = GetPoint(start, offset);
    var endPos = GetPoint(start, offset + length);

    textRange.Select(startPos, endPos);
    textRange.ApplyPropertyValue(TextElement.ForegroundProperty, 
new SolidColorBrush(color));
    textRange.ApplyPropertyValue(TextElement.FontWeightProperty, 
FontWeights.Bold);
}

Ответы

Ответ 1

Public Function GoToPoint(ByVal start As TextPointer, ByVal x As Integer) As TextPointer
    Dim out As TextPointer = start
    Dim i As Integer = 0
    Do While i < x
        If out.GetPointerContext(LogicalDirection.Backward) = TextPointerContext.Text Or _
             out.GetPointerContext(LogicalDirection.Backward) = TextPointerContext.None Then
            i += 1
        End If
        If out.GetPositionAtOffset(1, LogicalDirection.Forward) Is Nothing Then
            Return out
        Else
            out = out.GetPositionAtOffset(1, LogicalDirection.Forward)
        End If


    Loop
    Return out
End Function

Попробуйте это, это должно вернуть текстовый указатель для данного смещения char. (Извините, что в VB, но вот что я работаю...)

Ответ 2

Попробуйте следующее:

var textRange = MyRichTextBox.Selection;
var start = MyRichTextBox.Document.ContentStart;
var startPos = start.GetPositionAtOffset(3);
var endPos = start.GetPositionAtOffset(8);
textRange.Select(startPos, endPos);
textRange.ApplyPropertyValue(TextElement.ForegroundProperty, new SolidColorBrush(Colors.Blue));
textRange.ApplyPropertyValue(TextElement.FontWeightProperty, FontWeights.Bold);

Ответ 3

Я попытался использовать решение, отправленное KratzVB, но обнаружил, что он игнорирует новые строки. Если вы хотите считать символы \r и\n, то этот код должен работать:

private static TextPointer GetPoint(TextPointer start, int x)
{

        var ret = start;
        var i = 0;
        while (ret != null)
        {
            string stringSoFar = new TextRange(ret, ret.GetPositionAtOffset(i, LogicalDirection.Forward)).Text;
            if (stringSoFar.Length == x)
                    break;
            i++;
            if (ret.GetPositionAtOffset(i, LogicalDirection.Forward) == null)
                return ret.GetPositionAtOffset(i-1, LogicalDirection.Forward)

        }
        ret=ret.GetPositionAtOffset(i, LogicalDirection.Forward);
        return ret;
}

Ответ 4

Кстати, (и это может быть академическим для всех, кроме меня самого), если вы установите FocusManager.IsFocusScope = "True" в контейнере RichTextBox, например, Grid,

<Grid FocusManager.IsFocusScope="True">...</Grid>

тогда вы сможете использовать метод Johan Danforth Colorize без двух вызовов ApplyPropertyValue, а RichTextBox должен использовать выделение по умолчанию Background и Foreground, чтобы выделить выделение.

private void Colorize(int offset, int length, Color color)
{
    var textRange = MyRichTextBox.Selection;
    var start = MyRichTextBox.Document.ContentStart;
    var startPos = GetPoint(start, offset);
    var endPos = GetPoint(start, offset + length);

    textRange.Select(startPos, endPos);
}

Не пробовал это с RichTextBox, но он работает очень хорошо при шаблонизации поиска TextBox в FlowDocumentReader. Чтобы убедиться, что вы также можете установить

<RichTextBox FocusManager.FocusedElement="{Binding RelativeSource={RelativeSource Self}}">...</RichTextBox>

для обеспечения фокусировки RichTextBox в пределах его фокуса.

Недостатком этого, конечно же, является то, что если пользователь нажимает или выполняет выбор в RichTextBox, ваш выбор исчезает.

Ответ 5

Моя версия на основе версии cave_dweller

private static TextPointer GetPositionAtCharOffset(TextPointer start, int numbertOfChars)
{
    var offset = start;
    int i = 0;
    string stringSoFar="";
    while (stringSoFar.Length < numbertOfChars)
    {
        i++;
        TextPointer offsetCandidate = start.GetPositionAtOffset(
                i, LogicalDirection.Forward);

        if (offsetCandidate == null)
            return offset; // ups.. we are to far

        offset = offsetCandidate;
        stringSoFar = new TextRange(start, offset).Text;
    }

    return offset;
}

Чтобы опустить некоторые символы, добавьте этот код внутри цикла:

stringSoFar = stringSoFar.Replace("\r\n", "")
                         .Replace(" ", "")

Вместо этого (медленный):

var startPos = GetPoint(start, offset);
var endPos = GetPoint(start, offset + length);

Вы должны сделать это (быстрее)

var startPos = GetPoint(start, offset);
var endPos = GetPoint(startPos, length);

Или создайте отдельный метод для получения TextRange:

private static TextRange GetTextRange(TextPointer start, int startIndex, int length)
{
    var rangeStart = GetPositionAtCharOffset(start, startIndex);
    var rangeEnd = GetPositionAtCharOffset(rangeStart, length);
    return new TextRange(rangeStart, rangeEnd);
}

Теперь вы можете форматировать текст без Select() ing:

var range = GetTextRange(Document.ContentStart, 3, 8);
range.ApplyPropertyValue(
    TextElement.BackgroundProperty, 
    new SolidColorBrush(Colors.Aquamarine));

Ответ 6

Не удалось найти решение с приемлемым решением для решения этой проблемы в течение длительного времени. Следующий пример работает в моем случае с наивысшей производительностью. Надеюсь, это тоже поможет кому-то.

TextPointer startPos = rtb.Document.ContentStart.GetPositionAtOffset(searchWordIndex, LogicalDirection.Forward);
startPos = startPos.CorrectPosition(searchWord, FindDialog.IsCaseSensitive);
if (startPos != null)
{
    TextPointer endPos = startPos.GetPositionAtOffset(textLength, LogicalDirection.Forward);
    if (endPos != null)
    {
         rtb.Selection.Select(startPos, endPos);
    }
}

public static TextPointer CorrectPosition(this TextPointer position, string word, bool caseSensitive)
{
   TextPointer start = null;
   while (position != null)
   {
       if (position.GetPointerContext(LogicalDirection.Forward) == TextPointerContext.Text)
       {
           string textRun = position.GetTextInRun(LogicalDirection.Forward);

           int indexInRun = textRun.IndexOf(word, caseSensitive ? StringComparison.InvariantCulture : StringComparison.InvariantCultureIgnoreCase);
           if (indexInRun >= 0)
           {
               start = position.GetPositionAtOffset(indexInRun);
               break;
           }
       }

       position = position.GetNextContextPosition(LogicalDirection.Forward);
   }

   return start; 
}

Ответ 7

    private TextPointer GetPoint(TextPointer start, int pos)
    {
        var ret = start;
        int i = 0;
        while (i < pos)
        {
            if (ret.GetPointerContext(LogicalDirection.Forward) ==
    TextPointerContext.Text)
                i++;
            if (ret.GetPositionAtOffset(1, LogicalDirection.Forward) == null)
                return ret;
            ret = ret.GetPositionAtOffset(1, LogicalDirection.Forward);
        }
        return ret;
    }