WPF RichTextBox - 多个块选择

时间:2014-08-05 22:52:19

标签: c# wpf richtextbox

您好我正在开发一个涉及使用 RichTextBox 的WPF项目(使用/ C#)。我目前已完成大部分工作,但需要帮助通过鼠标选择获得多个块,例如设置TextAlignment或Margin。供您参考,以下内容将通过为 RichTextBox 插入符设置TextPointer,然后迭代Document.Blocks以获取一个 一个 块。个别街区。但我希望有一种方法可以让我获得的不仅仅是一个块。有人可以提供更多关于如何选择 多个 块(不同行上的每个块)的见解吗?

// XAML

<RichTextBox Name="rtb" HorizontalAlignment="Left">
   <FlowDocument>
      <Paragraph TextAlignment="Left" Margin="0.0">
         <Run Text="Hello"/>
      </Paragraph>
      <Paragraph TextAlignment="Right" Margin="0.0">
         <Run Text="World"/>
      </Paragraph>
      <Paragraph TextAlignment="Left" Margin="1.0">
         <Run Text="Hello"/>
      </Paragraph>
      <Paragraph TextAlignment="Right" Margin="1.0">
         <Run Text="World"/>
      </Paragraph>
   </FlowDocument>
</RichTextBox>


// Code Behind

var curCaret = rtb.CaretPosition;
Block curBlock = rtb.Document.Blocks.Where(x => x.ContentStart.CompareTo(curCaret) ==    -1 && x.ContentEnd.CompareTo(curCaret) == 1).FirstOrDefault();

1 个答案:

答案 0 :(得分:1)

如果您只是想找到与当前Blocks重叠的顶级selection range,您可以这样做:

public static class FlowDocumentHelper
{
    public static bool Overlaps(this TextElement element, TextPointer start, TextPointer end)
    {
        return element.ContentEnd.CompareTo(start) > 0 && element.ContentStart.CompareTo(end) < 0;
    }
}

然后

var blocks = richTextBox.Document.Blocks.Where(block => block.Overlaps(richTextBox.Selection.Start, richTextBox.Selection.End));

但是,如果您正在寻找与当前选择范围重叠的paragraphs,那么这将无效,因为它们可能深埋在文本元素层次结构中,例如figure内{在list内的table内{3}}。要实际发现与当前选择范围重叠的段落,您必须递归地遍历块的层次结构,而WPF没有提供直接的方法来执行此操作(尽管它们内部肯定有这些信息!)。

因此,可以为可能包含子节点的所有可能section类手工创建递归迭代器,或者遍历文档中的所有TextElement对象并使用它们来发现层次结构。以下使用后一种策略:

public static class FlowDocumentHelper
{
    public static IEnumerable<TTextElement> WalkTextRange<TTextElement>(this FlowDocument doc, TextPointer start, TextPointer end) where TTextElement : TextElement
    {
        var lastVisited = new Dictionary<int, TTextElement>();
        foreach (var stack in doc.WalkTextHierarchy())
        {
            var element = stack.Peek() as TTextElement;
            if (element != null)
            {
                TTextElement previous;
                if (!lastVisited.TryGetValue(stack.Count - 1, out previous) || previous != element)
                {
                    if (element.Overlaps(start, end))
                        yield return element;
                    lastVisited[stack.Count - 1] = element;
                }
            }
        }
    }

    public static bool Overlaps(this TextElement element, TextPointer start, TextPointer end)
    {
        return element.ContentEnd.CompareTo(start) > 0 && element.ContentStart.CompareTo(end) < 0;
    }

    public static IEnumerable<Stack<DependencyObject>> WalkTextHierarchy(this FlowDocument doc)
    {
        if (doc == null)
            throw new ArgumentNullException();

        var stack = new Stack<DependencyObject>();

        // Keep a TextPointer for FlowDocument.ContentEnd handy, so we know when we're done.
        TextPointer docEnd = doc.ContentEnd;

        // Keep going until the TextPointer is equal to or greater than ContentEnd.
        for (var iterator = doc.ContentStart; 
            (iterator != null) && (iterator.CompareTo(docEnd) < 0);
            iterator = iterator.GetNextContextPosition(LogicalDirection.Forward))
        {
            var parent = iterator.Parent;

            // Identify the type of content immediately adjacent to the text pointer.
            TextPointerContext context = iterator.GetPointerContext(LogicalDirection.Forward);

            switch (context)
            {
                case TextPointerContext.ElementStart:
                case TextPointerContext.EmbeddedElement:
                case TextPointerContext.Text:
                    PushElement(stack, parent);
                    yield return stack;
                    break;

                case TextPointerContext.ElementEnd:
                    break;

                default:
                    throw new System.Exception("Unhandled TextPointerContext " + context.ToString());
            }

            switch (context)
            {
                case TextPointerContext.ElementEnd:
                case TextPointerContext.EmbeddedElement:
                case TextPointerContext.Text:
                    PopToElement(stack, parent);
                    break;

                case TextPointerContext.ElementStart:
                    break;

                default:
                    throw new System.Exception("Unhandled TextPointerContext " + context.ToString());
            }
        }
    }

    static int IndexOf<T>(Stack<T> source, T value)
    {
        int index = 0;
        var comparer = EqualityComparer<T>.Default;
        foreach (T item in source)
        {
            if (comparer.Equals(item, value))
                return index;
            index++;
        }
        return -1;
    }

    static void PopToElement<T>(Stack<T> stack, T item)
    {
        for (int index = IndexOf(stack, item); index >= 0; index--)
            stack.Pop();
    }

    static void PushElement<T>(Stack<T> stack, T item)
    {
        PopToElement(stack, item);
        stack.Push(item);
    }
}

然后

var paragraphs = richTextBox.Document.WalkTextRange<Paragraph>(richTextBox.Selection.Start, richTextBox.Selection.End);

(注意 - 经过适度测试的非生产代码。)

最后,如果你想允许你的用户按Ctrl键选择多个不相邻的文本范围,就像在Word中所描述的那样:TextPointer,那么我认为你已经出局了好运RichTextBox似乎不支持此用户交互。