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?
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.
TextBlock is not editable.
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>
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).
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}" />
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" />
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With