Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I determine if my TextBlock text is being trimmed?

Tags:

wpf

textblock

The following textblock wraps and trims as expected. The elipsis "..." is displayed when the text is trimmed.

<TextBlock 
    MaxWidth="60" 
    MaxHeight="60" 
    Text="This is some long text which I would like to wrap."
    TextWrapping="Wrap" 
    TextTrimming="CharacterEllipsis" />

I would like to display a tooltip over the text with the full text, but only if the text is trimmed. I'm not sure how to reliably determine if the "..." is being shown or not.

How do I determine if the text is being trimmed or not?

like image 745
Andrew Jackson Avatar asked Jun 25 '09 01:06

Andrew Jackson


People also ask

What is text trimming?

TRIM will remove extra spaces from text. Thus, it will leave only single spaces between words and no space characters at the start or end of the text. It is very useful when cleaning up text from other applications or environments. TRIM only removes the ASCII space character (32) from the text.

Is TextBlock editable?

TextBlock is not editable.


4 Answers

Because the link in Alek's answer is down, I found a cached copy of the link from the wayback machine. You can not download the code linked in the article, so here is a pre-assembled version of the code. There was one or two issues I ran in to while trying to make it work so this code is slightly different then the code in the examples in the article.

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;

namespace TextBlockService
{
    //Based on the project from http://web.archive.org/web/20130316081653/http://tranxcoder.wordpress.com/2008/10/12/customizing-lookful-wpf-controls-take-2/
    public static class TextBlockService
    {
        static TextBlockService()
        {
            // Register for the SizeChanged event on all TextBlocks, even if the event was handled.
            EventManager.RegisterClassHandler(
                typeof(TextBlock),
                FrameworkElement.SizeChangedEvent,
                new SizeChangedEventHandler(OnTextBlockSizeChanged),
                true);
        }


        private static readonly DependencyPropertyKey IsTextTrimmedKey = DependencyProperty.RegisterAttachedReadOnly("IsTextTrimmed", 
            typeof(bool), 
            typeof(TextBlockService), 
            new PropertyMetadata(false));

        public static readonly DependencyProperty IsTextTrimmedProperty = IsTextTrimmedKey.DependencyProperty;

        [AttachedPropertyBrowsableForType(typeof(TextBlock))]
        public static Boolean GetIsTextTrimmed(TextBlock target)
        {
            return (Boolean)target.GetValue(IsTextTrimmedProperty);
        }


        public static readonly DependencyProperty AutomaticToolTipEnabledProperty = DependencyProperty.RegisterAttached(
            "AutomaticToolTipEnabled",
            typeof(bool),
            typeof(TextBlockService),
            new FrameworkPropertyMetadata(true, FrameworkPropertyMetadataOptions.Inherits));

        [AttachedPropertyBrowsableForType(typeof(DependencyObject))]
        public static Boolean GetAutomaticToolTipEnabled(DependencyObject element)
        {
            if (null == element)
            {
                throw new ArgumentNullException("element");
            }
            return (bool)element.GetValue(AutomaticToolTipEnabledProperty);
        }

        public static void SetAutomaticToolTipEnabled(DependencyObject element, bool value)
        {
            if (null == element)
            {
                throw new ArgumentNullException("element");
            }
            element.SetValue(AutomaticToolTipEnabledProperty, value);
        }

        private static void OnTextBlockSizeChanged(object sender, SizeChangedEventArgs e)
        {
            TriggerTextRecalculation(sender);
        }

        private static void TriggerTextRecalculation(object sender)
        {
            var textBlock = sender as TextBlock;
            if (null == textBlock)
            {
                return;
            }

            if (TextTrimming.None == textBlock.TextTrimming)
            {
                textBlock.SetValue(IsTextTrimmedKey, false);
            }
            else
            {
                //If this function is called before databinding has finished the tooltip will never show.
                //This invoke defers the calculation of the text trimming till after all current pending databinding
                //has completed.
                var isTextTrimmed = textBlock.Dispatcher.Invoke(() => CalculateIsTextTrimmed(textBlock), DispatcherPriority.DataBind);
                textBlock.SetValue(IsTextTrimmedKey, isTextTrimmed);
            }
        }

        private static bool CalculateIsTextTrimmed(TextBlock textBlock)
        {
            if (!textBlock.IsArrangeValid)
            {
                return GetIsTextTrimmed(textBlock);
            }

            Typeface typeface = new Typeface(
                textBlock.FontFamily,
                textBlock.FontStyle,
                textBlock.FontWeight,
                textBlock.FontStretch);

            // FormattedText is used to measure the whole width of the text held up by TextBlock container
            FormattedText formattedText = new FormattedText(
                textBlock.Text,
                System.Threading.Thread.CurrentThread.CurrentCulture,
                textBlock.FlowDirection,
                typeface,
                textBlock.FontSize,
                textBlock.Foreground);

            formattedText.MaxTextWidth = textBlock.ActualWidth;

            // When the maximum text width of the FormattedText instance is set to the actual
            // width of the textBlock, if the textBlock is being trimmed to fit then the formatted
            // text will report a larger height than the textBlock. Should work whether the
            // textBlock is single or multi-line.
            // The "formattedText.MinWidth > formattedText.MaxTextWidth" check detects if any 
            // single line is too long to fit within the text area, this can only happen if there is a 
            // long span of text with no spaces.
            return (formattedText.Height > textBlock.ActualHeight || formattedText.MinWidth > formattedText.MaxTextWidth);
        }

    }
}
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:tbs="clr-namespace:TextBlockService">
    <!--
    Rather than forcing *all* TextBlocks to adopt TextBlockService styles,
    using x:Key allows a more friendly opt-in model.
    -->

    <Style TargetType="TextBlock" x:Key="TextBlockService">
        <Style.Triggers>
            <MultiTrigger>
                <MultiTrigger.Conditions>
                    <Condition Property="tbs:TextBlockService.AutomaticToolTipEnabled" Value="True" />
                    <Condition Property="tbs:TextBlockService.IsTextTrimmed" Value="True"/>
                </MultiTrigger.Conditions>

                <Setter Property="ToolTip" Value="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=Text}" />
            </MultiTrigger>
        </Style.Triggers>
    </Style>
</ResourceDictionary>
like image 111
Scott Chamberlain Avatar answered Nov 13 '22 13:11

Scott Chamberlain


I haven't done a lot of WPF lately, so I'm not sure if this is what you're looking for, but check out this article: Customizing “lookful” WPF controls – Take 2. It's a bit complex, but it seems to address the same question you're asking. UPDATE: The website seems gone, but you can find the article in the archive. SEE Scott Chamberlain's ANSWER WITH THE SAMPLE CODE (thanks Scott).

like image 41
Alek Davis Avatar answered Nov 13 '22 13:11

Alek Davis


The solution above didn't work for me if the TextBlock is part of a ListBoxItem DataTemplate. I propose another solution:

public class MyTextBlock : System.Windows.Controls.TextBlock
{

    protected override void OnToolTipOpening(WinControls.ToolTipEventArgs e)
    {
        if (TextTrimming != TextTrimming.None)
        {
            e.Handled = !IsTextTrimmed(); 
        }
    }

    private bool IsTextTrimmed()
    {
        Measure(new Size(Double.PositiveInfinity, Double.PositiveInfinity));
        return ActualWidth < DesiredSize.Width;
    }
}

XAML:

  <MyTextBlock Text="{Binding Text}" TextTrimming="CharacterEllipsis" ToolTip="{Binding Text}" />
like image 40
Ketobomb Avatar answered Nov 13 '22 14:11

Ketobomb


Expanding on bidy's answer. This will create a TextBlock that only shows the tooltip when not all text is shown. The tooltip will be resized to the contents (as opposed to the default tooltip which will remain a one-line box with the text cut off).

using System;
using System.Windows;
using System.Windows.Controls;

namespace MyComponents
{
    public class CustomTextBlock : TextBlock
    {
        protected override void OnInitialized(EventArgs e)
        {
            // we want a tooltip that resizes to the contents -- a textblock with TextWrapping.Wrap will do that
            var toolTipTextBlock = new TextBlock();
            toolTipTextBlock.TextWrapping = TextWrapping.Wrap;
            // bind the tooltip text to the current textblock Text binding
            var binding = GetBindingExpression(TextProperty);
            if (binding != null)
            {
                toolTipTextBlock.SetBinding(TextProperty, binding.ParentBinding);
            }

            var toolTipPanel = new StackPanel();
            toolTipPanel.Children.Add(toolTipTextBlock);
            ToolTip = toolTipPanel;

            base.OnInitialized(e);
        }

        protected override void OnToolTipOpening(ToolTipEventArgs e)
        {
            if (TextTrimming != TextTrimming.None)
            {
                e.Handled = !IsTextTrimmed();
            }
        }

        private bool IsTextTrimmed()
        {
            Measure(new Size(Double.PositiveInfinity, Double.PositiveInfinity));
            return ActualWidth < DesiredSize.Width;
        }
    }
}

XAML usage:

    <Window ...
        xmlns:components="clr-namespace:MyComponents"
     ... >
    
    <components:CustomTextBlock Text="{Binding Details}" TextTrimming="CharacterEllipsis" />
like image 33
CameO73 Avatar answered Nov 13 '22 12:11

CameO73