How can I determine if my TextBlock text is being trimmed?
Solution 1:
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>
Solution 2:
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).
Solution 3:
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}" />