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

后端 未结 5 1591
被撕碎了的回忆
被撕碎了的回忆 2020-11-30 01:02

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



        
相关标签:
5条回答
  • 2020-11-30 01:35

    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>
    
    0 讨论(0)
  • 2020-11-30 01:45

    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).

    0 讨论(0)
  • 2020-11-30 01:48

    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}" />
    
    0 讨论(0)
  • 2020-11-30 01:48

    I had a small issue using Alex answer and had to change my logic a bit to clarify if the text in the text block was being trimmed.

    var formattedText = new FormattedText(
                Text, System.Threading.Thread.CurrentThread.CurrentCulture, FlowDirection, typeface, FontSize,
                Foreground, VisualTreeHelper.GetDpi( this ).PixelsPerDip ) { MaxTextWidth = ActualWidth };
            //Measure(new Size(Double.PositiveInfinity, Double.PositiveInfinity));
    
     return ( Math.Floor(formattedText.Height ) > ActualHeight || Math.Floor( formattedText.MinWidth ) > ActualWidth;
    

    This works perfectly for me.

    I defined a user control that was a TextBlock with ellipsis enabled. Then I defined 2 functions for OnMouseUp and OnMouseDown, so that when the user clicked on the textblock that had overflow it would display a tooltip with the full value.

    This is the OnMouseDown function

    private void TextBlockWithToolTipView_OnMouseDown(
            object sender,
            MouseButtonEventArgs e )
        {
            var typeface = new Typeface(
                FontFamily,
                FontStyle,
                FontWeight,
                FontStretch);
    
            var formattedText = new FormattedText(
                Text, System.Threading.Thread.CurrentThread.CurrentCulture, FlowDirection, typeface, FontSize,
                Foreground, VisualTreeHelper.GetDpi( this ).PixelsPerDip ) { MaxTextWidth = ActualWidth };
    
            if (Math.Floor(formattedText.Height) > ActualHeight || Math.Floor(formattedText.MinWidth) > ActualWidth )
            {
                if( ToolTip is ToolTip tt )
    
                {
                    {
                        if( tt.PlacementTarget == null )
                        {
                            tt.PlacementTarget = this;
                        }
    
                        tt.IsOpen = true;
                        e.Handled = true;
                    }
                }
            }
        }
    

    And this was the Xaml bit

    <TextBlock 
             ToolTipService.IsEnabled="True"
             MouseDown="TextBlockWithToolTipView_OnMouseDown"
             MouseLeave="TextBlockWithToolTipView_OnMouseLeave"   
             TextTrimming="CharacterEllipsis"
             TextWrapping="WrapWithOverflow">
    <TextBlock.ToolTip>
            <ToolTip 
                DataContext="{Binding Path=PlacementTarget, RelativeSource={x:Static RelativeSource.Self}}">
            <TextBlock Text="{Binding Path=Text, Mode=OneWay }"
                           TextWrapping="Wrap"/>
            </ToolTip>
        </TextBlock.ToolTip>
    </TextBlock>
    
    0 讨论(0)
  • 2020-11-30 01:49

    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 Optimizers.App4Sales.LogViewer.Components
    {
        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" />
    
    0 讨论(0)
提交回复
热议问题