Get overflown text of FormattedText

564 Views Asked by At

I want to obtain the overflown text (i.e. the substring after the ellipsis) after setting FormattedText.MaxTextWidth and FormattedText.MaxTextHeight. Is there an elegant way to achieve this? This seems especially diffult since FormattedText may contain different font families, font sizes etc.

2

There are 2 best solutions below

0
Fred Nek On BEST ANSWER

After thinking about this problem for a little more, I've come up with this solution (which returns the index of the first :

/// <summary>
/// Retrieves the index at which the text flows over (the first index that is trimmed)
/// </summary>
/// <param name="text"></param>
/// <returns></returns>
public static int GetOverflowIndex(FormattedText text)
{
    // Early out: No overflow
    if (text.BuildHighlightGeometry(new Point(0, 0), text.Text.Length - 1, 1) != null)
        return -1;

    int sublen = text.Text.Length;
    int offset = 0;
    int index = 0;

    while (sublen > 1)
    {
        string debugStr = text.Text.Substring(offset, sublen);
        index = offset + sublen / 2;
        Geometry characterGeometry = text.BuildHighlightGeometry(new Point(0, 0), index, 1);

        // Geometry is null, if the character is overflown
        if (characterGeometry != null)
        {
            offset = index;
            sublen = sublen - sublen / 2;
        }
        else
        {
            sublen /= 2;
        }
    }

    return index;
}
0
Tam Bui On

Hmm, this was a tough one. I can get it very close, but it's not 100% accurate. However, perhaps you can use this as a starting point.

Example Output for this string: "This is some really long text that cannot fit within the width specified!"

enter image description here

The Approach:

Basically I wrote a while loop that would check the actual formatted text's width when I fed it some text. If the width exceeded the width of the one that was displaying the ellipsis, then I would strip out the last character and check again and again and again until it fit.

MainWindow.xaml:

<Window x:Class="GetOverflowTextTest.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <StackPanel Orientation="Vertical">
            <StackPanel Orientation="Horizontal">
                <RadioButton x:Name="radioButtonArial" Content="Arial Size 14" GroupName="Fonts" Click="ArialClick" Margin="5" IsChecked="True"/>
                <RadioButton x:Name="radioButtonTimesNewRoman" Content="Times New Roman Size 32" GroupName="Fonts" Click="TimesNewRomanClick" Margin="5"/>
            </StackPanel>
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="Auto"/>
                    <ColumnDefinition/>
                </Grid.ColumnDefinitions>
                <Grid.RowDefinitions>
                    <RowDefinition/>
                    <RowDefinition/>
                </Grid.RowDefinitions>
                <TextBlock Grid.Row="0" Grid.Column="0" Text="Long Text Block With Ellipsis (Width 200): " Margin="5" HorizontalAlignment="Right"/>
                <TextBlock Grid.Row="0" Grid.Column="1" x:Name="myTextBlock" Width="200" Margin="5" TextWrapping="NoWrap" TextTrimming="CharacterEllipsis" Background="DarkGreen" Foreground="White" HorizontalAlignment="Left" />
                <TextBlock Grid.Row="1" Grid.Column="0" Text="Here's your overflow text: " Margin="5" HorizontalAlignment="Right"/>
                <TextBlock Grid.Row="1" Grid.Column="1" x:Name="myOverflowTextBlock" Margin="5" TextWrapping="NoWrap" HorizontalAlignment="Left"/>
            </Grid>
            <StackPanel Orientation="Horizontal">
            </StackPanel>
            <StackPanel Orientation="Horizontal">
            </StackPanel>
        </StackPanel>
    </Grid>
</Window>

MainWindow.xaml.cs:

using System.Globalization;
using System.Windows;
using System.Windows.Media;

namespace GetOverflowTextTest
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        private const string TEXT = "This is some really long text that cannot fit within the width specified!";

        public MainWindow()
        {
            InitializeComponent();
            this.Loaded += OnLoaded;
            this.myTextBlock.Text = TEXT;
        }

        private void OnLoaded(object sender, RoutedEventArgs e)
        {
            UpdateFont();
        }

        private void UpdateFont()
        {
            if (this.radioButtonArial.IsChecked.HasValue && this.radioButtonArial.IsChecked.Value)
            {
                // Change the font to Arial
                this.myTextBlock.FontFamily = new FontFamily("Arial");
                this.myTextBlock.FontSize = 14;
            }
            else
            {
                // Change the font to Times New Roman
                this.myTextBlock.FontFamily = new FontFamily("Times New Roman");
                this.myTextBlock.FontSize = 32;
            }

            // Calculate the overflow text using the font, and then update the result.
            CalculateAndUpdateOverflowText();
        }

        private void CalculateAndUpdateOverflowText()
        {
            // Start with the full text.
            var displayedText = TEXT;

            // Now start trimming until the width shrinks to the width of myTextBlock.
            var fullFormattedText = new FormattedText(displayedText, CultureInfo.InvariantCulture, FlowDirection.LeftToRight, new Typeface(this.myTextBlock.FontFamily, myTextBlock.FontStyle, myTextBlock.FontWeight, myTextBlock.FontStretch), myTextBlock.FontSize, new SolidColorBrush(Colors.Black), 1.0);

            while (fullFormattedText.Width > this.myTextBlock.Width)
            {
                displayedText = displayedText.Remove(displayedText.Length - 1, 1);
                fullFormattedText = new FormattedText(displayedText, CultureInfo.InvariantCulture, FlowDirection.LeftToRight, new Typeface(this.myTextBlock.FontFamily, myTextBlock.FontStyle, myTextBlock.FontWeight, myTextBlock.FontStretch), myTextBlock.FontSize, new SolidColorBrush(Colors.Black), 1.0);
            }

            // What you have left is the displayed text.  Remove it from the overall string to get the remainder overflow text.
            // The reason why I added "- 3" is because there are three ellipsis characters that cover up some of the text that would have otherwise been displayed.
            var overflowText = TEXT.Remove(0, displayedText.Length - 3);

            // Update the text block
            this.myOverflowTextBlock.Text = overflowText;
        }

        private void ArialClick(object sender, RoutedEventArgs e)
        {
            UpdateFont();
        }

        private void TimesNewRomanClick(object sender, RoutedEventArgs e)
        {
            UpdateFont();
        }
    }
}