0

In a UWP app, I am using a RichTextBlock that gets populated with some content. It has word wrapping enabled and has a max lines set so that regardless of the length of its content, it will only show a certain number of lines of rich text.

I'd like to know if there is a way to figure out what is the visible text?

So if I have:

<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>

I'd like to know how much of the text is actually visible.

I'm trying to detect cases where the text is longer than a set number of lines and append a "... Read More" at the end of the last line (replacing the last 13 chars with "... Read More")

  • You have to use "... Read More"? Would "..." be OK? – Scavenger Jan 26 '17 at 08:42
  • Yes it has to be "... Read More" otherwise I would have just used TextTrimming = CharacterEllipsis Unfortunately there isn't a way to provide custom text to be used instead of "..." when using TextTrimming, which would have solved my problem. – Meisam Seyed Aliroteh Jan 26 '17 at 18:51

1 Answers1

1

So I wrote some code to get the behavior that I want, but unfortunately this is rather slow and inefficient. So if you're using it in an app that is primarily to show a lot of text that needs to be truncated (like a ListView with a lot of text items) then this would slow down your app perf. I still would like to know if there is a better way to do this.

Here's my code (which only handles Run and Hyperlink inlines so you'll have to modify to handle other types that you need):

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;
            }
        }
    }
}