从 RichTextBlock 获取可见文本

Get Visible Text from RichTextBlock

在 UWP 应用程序中,我使用了填充了一些内容的 RichTextBlock。它启用了自动换行并设置了最大行数,因此无论其内容的长度如何,它都只会显示一定数量的富文本行。

我想知道是否有办法弄清楚可见文本是什么?

所以如果我有:

<RichTextBlock TextWrapping="Wrap" MaxLines="2">
    <RichTextBlock.Blocks>
        <Paragraph>
            <Paragraph.Inlines>
                A bunch of runs go in here with text that are several lines
            </Paragraph.Inlines>
        </Paragraph>
    </RichTextBlock.Blocks>
</RichTextBlock>

我想知道有多少文本是实际可见的。

我正在尝试检测文本长于设定行数的情况,并在最后一行的末尾附加一个“...阅读更多”(将最后 13 个字符替换为“.. . 阅读更多")

所以我写了一些代码来获得我想要的行为,但不幸的是,这相当缓慢且效率低下。因此,如果您在主要用于显示大量需要截断的文本(如包含大量文本项的 ListView)的应用程序中使用它,那么这会降低您的应用程序性能。我仍然想知道是否有更好的方法来做到这一点。

这是我的代码(仅处理 运行 和超链接内联,因此您必须修改以处理您需要的其他类型):

private static void TrimText_Slow(RichTextBlock rtb)
{
    var paragraph = rtb?.Blocks?.FirstOrDefault() as Paragraph;
    if (paragraph == null) { return; }

    // Ensure RichTextBlock has passed a measure step so that its HasOverflowContent is updated.
    rtb.Measure(new Size(Double.PositiveInfinity, Double.PositiveInfinity));
    if (rtb.HasOverflowContent == false) { return; }


    // Start from end and remove all inlines that are not visible
    Inline lastInline = null;
    var idx = paragraph.Inlines.Count - 1;
    while (idx >= 0 && rtb.HasOverflowContent)
    {
        lastInline = paragraph.Inlines[idx];
        paragraph.Inlines.Remove(lastInline);
        idx--;
        // Ensure RichTextBlock has passed a measure step now with an inline removed, so that its HasOverflowContent is updated.
        rtb.Measure(new Size(Double.PositiveInfinity, Double.PositiveInfinity));
    }

    // The last inline could be partially visible. The easiest thing to do here is to always
    // add back the last inline and then remove characters from it until everything is in view.
    if (lastInline != null)
    {
        paragraph.Inlines.Add(lastInline);
    }

    // Make room to insert "... Read More"
    DeleteCharactersFromEnd(paragraph.Inlines, 13);

    // Insert "... Continue Reading"
    paragraph.Inlines.Add(new Run { Text = "... " });
    paragraph.Inlines.Add(new Run { Text = "Read More", Foreground = new SolidColorBrush(Colors.Blue) });

    // Ensure RichTextBlock has passed a measure step now with the new inlines added, so that its HasOverflowContent is updated.
    rtb.Measure(new Size(Double.PositiveInfinity, Double.PositiveInfinity));

    // Keep deleting chars until "... Continue Reading" comes into view
    idx = paragraph.Inlines.Count - 3; // skip the last 2 inlines since they are "..." and "Read More"
    while (idx >= 0 && rtb.HasOverflowContent)
    {
        Run run;

        if (paragraph.Inlines[idx] is Hyperlink)
        {
            run = ((Hyperlink)paragraph.Inlines[idx]).Inlines.FirstOrDefault() as Run;
        }
        else
        {
            run = paragraph.Inlines[idx] as Run;
        }

        if (string.IsNullOrEmpty(run?.Text))
        {
            paragraph.Inlines.Remove(run);
            idx--;
        }
        else
        {
            run.Text = run.Text.Substring(0, run.Text.Length - 1);
        }

        // Ensure RichTextBlock has passed a measure step now with the new inline content updated, so that its HasOverflowContent is updated.
        rtb.Measure(new Size(Double.PositiveInfinity, Double.PositiveInfinity));
    }
}

private static void DeleteCharactersFromEnd(InlineCollection inlines, int numCharsToDelete)
{
    if (inlines == null || inlines.Count < 1 || numCharsToDelete < 1) { return; }

    var idx = inlines.Count - 1;

    while (numCharsToDelete > 0)
    {
        Run run;

        if (inlines[idx] is Hyperlink)
        {
            run = ((Hyperlink)inlines[idx]).Inlines.FirstOrDefault() as Run;
        }
        else
        {
            run = inlines[idx] as Run;
        }

        if (run == null)
        {
            inlines.Remove(inlines[idx]);
            idx--;
        }
        else
        {
            var textLength = run.Text.Length;
            if (textLength <= numCharsToDelete)
            {
                numCharsToDelete -= textLength;
                inlines.Remove(inlines[idx]);
                idx--;
            }
            else
            {
                run.Text = run.Text.Substring(0, textLength - numCharsToDelete);
                numCharsToDelete = 0;
            }
        }
    }
}