Search code examples
c#wpftextblocktexttrimming

WPF How to get hidden text range of trimmed text (with TextTrimming enabled)


How to get the trimmed text of the TextBlock when TextTrimming is set to "WordEllipsis" or "CharacterEllipsis" ?

For example, this code:

<TextBlock Name="text1" FontSize="12" Width="94" Text="THIS IS A LONG TEXT" TextTrimming="WordEllipsis"/>

displays: THIS IS A...

How to get the trimmed text: LONG TEXT or the index where the string is trimmed: 10


Solution

  • You need to manually measure the text (with all the font styling applied).

    A general solution that also works with TextTrimming set to TextTrimming.WordEllipsis would have to iteratively find the last character position.
    Because there is nor related API we must do it the hard way and iteratively test until we don't get an exception anymore. There is no other way (the requirement to known the exact text portion that clips the available text area is usually not sensible).

    However, when TextTrimming is set to TextTrimming.CharacterEllipsis there won't be any exceptions thrown. This is because character based trimming produces a predictable result opposed to word trimming where the length of the words is random (because the words are not known). This means the TextTrimming.CharacterEllipsis based case is more performance friendly.

    Additionally, finding the multiple hidden text ranges of a multi-line TextBlock is a more complex scenario that is not addressed by the proposed solution.

    The final solution that supports TextTrimming.WordEllipsis and TextTrimming.CharacterEllipsis could look as follows:

    public string GetHiddenRangeOfTrimmedText(TextBlock textBlock)
    {
      var typeFace = new Typeface(textBlock.FontFamily, textBlock.FontStyle, textBlock.FontWeight, textBlock.FontStretch);
      var numberSubstitution = new NumberSubstitution(NumberSubstitution.GetCultureSource(textBlock), CultureInfo.CurrentUICulture, NumberSubstitution.GetSubstitution(textBlock));
    
      // Define ellipsis using four ellipsis characters (instead of three).
      // This is because ellipsis is rendered using three visible and one hidden ellipsis characters.
      var formattedEllipsisText = new FormattedText(
        "....",
        CultureInfo.CurrentUICulture,
        textBlock.FlowDirection,
        typeFace,
        textBlock.FontSize,
        textBlock.Foreground,
        numberSubstitution,
        VisualTreeHelper.GetDpi(textBlock).PixelsPerDip);
      double clippingOffset = textBlock.ActualWidth - formattedEllipsisText.Width - textBlock.Padding.Right;
      var clippingPosition = new Point(clippingOffset, 0);
    
      TextPointer clippingPositionTextPointer = null;
      do
      {
        try
        {
          clippingPositionTextPointer = textBlock.GetPositionFromPoint(clippingPosition, true);
        }
        catch (ArgumentException)
        {
          if (clippingPosition.X == 0)
          {
            return string.Empty;
          }
    
          clippingPosition.Offset(-1, 0);
        }
    
        if (clippingPositionTextPointer is not null)
        {
          break;
        }
      } while (clippingPosition.X >= 0);
    
      var clippedTextRange = new TextRange(clippingPositionTextPointer, textBlock.ContentEnd);
      string clippedText = clippedTextRange.Text;
          
      return clippedText;
    }
    
    public int GetIndexOfHiddenRangeOfTrimmedText(TextBlock textBlock)
    {
      string hiddenTextRange = GetHiddenRangeOfTrimmedText(textBlock);
      int indexOfHiddenTextRange = textBlock.Text.IndexOf(hiddenTextRange, StringComparison.OrdinalIgnoreCase);
      return indexOfHidenTextRange;
    }