テキストがトリミングされているときにツールチップを表示する

c# texttrimming tooltip wpf xaml
テキストがトリミングされているときにツールチップを表示する

テキストがトリミングされたときにのみ「ToolTip」を表示するにはどうすればよいですか? Windowsデスクトップのショートカットアイコンのように。

  15  5


ベストアンサー

Eyjafjの作業…​どんなアイデアでも、私は少なくともカスタムコントロールを必要としない、ほとんど宣言的なソリューションに到達しました。 克服すべき最初のハードルはTextBlockに到達することです。 ToolTipはビジュアルツリーの外部でレンダリングされるため、RelativeSourceバインディングまたはElementNameを使用してTextBlockを取得することはできません。 幸いなことに、ToolTipクラスはPlacementTargetプロパティを介して関連要素への参照を提供します。 したがって、ToolTipのVisibilityプロパティをToolTip自体にバインドし、PlacementTargetプロパティを使用してTextBlockのプロパティにアクセスできます。

次の手順では、コンバーターを使用してバインドしたTextBlockを調べ、ToolTipを表示するかどうかを決定します。 ActualWidthとDesiredSizeを使用してこれを行うことができます。 ActualWidthはまさにそのように聞こえます。 TextBlockが画面上にレンダリングされた幅。 DesiredSizeは、TextBlockが好む幅です。 唯一の問題は、DesiredSizeがTextTrimmingを考慮に入れているようであり、トリミングされていないテキスト全体の幅が得られないことです。 これを解決するために、Double.Positive infinityを渡すMeasureメソッドを呼び出して、事実上、TextBlockの幅が制限されていない場合の幅を尋ねることができます。 これによりDesiredSizeプロパティが更新され、比較が可能になります。

textBlock.Measure(new Size(Double.PositiveInfinity, Double.PositiveInfinity));

if (((FrameworkElement)value).ActualWidth < ((FrameworkElement)value).DesiredSize.Width)
    return Visibility.Visible;

このアプローチは実際にhttp://www.scottlogic.com/blog/2011/01/31/automatically-showing-tooltips-on-a-trimmed-textblock-silverlight-wpf.html [ここに添付された動作として] if TextBlocksに自動的に適用するか、常に非表示になるツールチップの作成にリソースを浪費したくない場合。 これが私の例の完全なコードです:

コンバーター:

public class TrimmedTextBlockVisibilityConverter : IValueConverter
{

    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        if (value == null) return Visibility.Collapsed;

        FrameworkElement textBlock = (FrameworkElement)value;

        textBlock.Measure(new System.Windows.Size(Double.PositiveInfinity, Double.PositiveInfinity));

        if (((FrameworkElement)value).ActualWidth < ((FrameworkElement)value).DesiredSize.Width)
            return Visibility.Visible;
        else
            return Visibility.Collapsed;
    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

XAML:

14


このページのアイデアに基づいて、https://stackoverflow.com/a/25436070からの追加のアルゴリズム修正を使用して[別の回答]非常に簡単に使用できるこの非常に移植性の高いクラスを作成しました。 その目的は、多くのアプリケーションで知られているように、テキストをトリミングするときに、トリミングを有効にし、TextBlockにツールチップを表示することです。

私のアプリケーションでは、トリミングの検出が正確であることが証明されています。 ツールチップは、トリミングの省略記号が表示されているときに正確に表示されます。

C#の使用

var textBlock = new TextBlock { Text = "Demo" };
TextBlockAutoToolTip.SetEnabled(textBlock, true);

完全なクラス

using System;
using System.Globalization;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Media;

namespace Unclassified.UI
{
    ///
    /// Shows a ToolTip over a TextBlock when its text is trimmed.
    ///
    public class TextBlockAutoToolTip
    {
        ///
        /// The Enabled attached property.
        ///
        public static readonly DependencyProperty EnabledProperty = DependencyProperty.RegisterAttached(
            "Enabled",
            typeof(bool),
            typeof(TextBlockAutoToolTip),
            new FrameworkPropertyMetadata(new PropertyChangedCallback(OnAutoToolTipEnabledChanged)));

        ///
        /// Sets the Enabled attached property on a TextBlock control.
        ///
        /// The TextBlock control.
        /// The value.
        public static void SetEnabled(DependencyObject dependencyObject, bool enabled)
        {
            dependencyObject.SetValue(EnabledProperty, enabled);
        }

        private static readonly TrimmedTextBlockVisibilityConverter ttbvc = new TrimmedTextBlockVisibilityConverter();

        private static void OnAutoToolTipEnabledChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs args)
        {
            TextBlock textBlock = dependencyObject as TextBlock;
            if (textBlock != null)
            {
                bool enabled = (bool)args.NewValue;
                if (enabled)
                {
                    var toolTip = new ToolTip
                    {
                        Placement = System.Windows.Controls.Primitives.PlacementMode.Relative,
                        VerticalOffset = -3,
                        HorizontalOffset = -5,
                        Padding = new Thickness(4, 2, 4, 2),
                        Background = Brushes.White
                    };
                    toolTip.SetBinding(UIElement.VisibilityProperty, new System.Windows.Data.Binding
                    {
                        RelativeSource = new System.Windows.Data.RelativeSource(System.Windows.Data.RelativeSourceMode.Self),
                        Path = new PropertyPath("PlacementTarget"),
                        Converter = ttbvc
                    });
                    toolTip.SetBinding(ContentControl.ContentProperty, new System.Windows.Data.Binding
                    {
                        RelativeSource = new System.Windows.Data.RelativeSource(System.Windows.Data.RelativeSourceMode.Self),
                        Path = new PropertyPath("PlacementTarget.Text")
                    });
                    toolTip.SetBinding(Control.ForegroundProperty, new System.Windows.Data.Binding
                    {
                        RelativeSource = new System.Windows.Data.RelativeSource(System.Windows.Data.RelativeSourceMode.Self),
                        Path = new PropertyPath("PlacementTarget.Foreground")
                    });
                    textBlock.ToolTip = toolTip;
                    textBlock.TextTrimming = TextTrimming.CharacterEllipsis;
                }
            }
        }

        private class TrimmedTextBlockVisibilityConverter : IValueConverter
        {
            // Source 1: https://stackoverflow.com/a/21863054
            // Source 2: https://stackoverflow.com/a/25436070

            public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
            {
                var textBlock = value as TextBlock;
                if (textBlock == null)
                    return Visibility.Collapsed;

                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,
                    VisualTreeHelper.GetDpi(textBlock).PixelsPerDip);

                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 width 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.
                bool isTrimmed = formattedText.Height > textBlock.ActualHeight ||
                    formattedText.MinWidth > formattedText.MaxTextWidth;

                return isTrimmed ? Visibility.Visible : Visibility.Collapsed;
            }

            public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
            {
                throw new NotImplementedException();
            }
        }
    }
}

7


TextBlockを拡張し、テキストの長さを比較して、ツールチップを表示するかどうかを判断する最も簡単なソリューションを見つけました。

public class ToolTipTextBlock : TextBlock
   {
      protected override void OnToolTipOpening(ToolTipEventArgs e)
      {
         if (TextTrimming != TextTrimming.None)
         {
           e.Handled = !IsTextTrimmed();
         }
      }

      private bool IsTextTrimmed()
      {
         var typeface = new Typeface(FontFamily, FontStyle, FontWeight, FontStretch);
         var formattedText = new FormattedText(Text, CultureInfo.CurrentCulture, FlowDirection, typeface, FontSize, Foreground);
         return formattedText.Width > ActualWidth;
      }
   }

次に、xamlでこのカスタムテキストブロックを次のように使用します。

6


行動は愛であり、行動は人生です。

public class TextBlockAutoToolTipBehavior : Behavior
{
    private ToolTip _toolTip;

    protected override void OnAttached()
    {
        base.OnAttached();
        _toolTip = new ToolTip
        {
            Placement = PlacementMode.Relative,
            VerticalOffset = 0,
            HorizontalOffset = 0
        };

        ToolTipService.SetShowDuration(_toolTip, int.MaxValue);

        _toolTip.SetBinding(ContentControl.ContentProperty, new Binding
        {
            Path = new PropertyPath("Text"),
            Source = AssociatedObject
        });

        AssociatedObject.TextTrimming = TextTrimming.CharacterEllipsis;
        AssociatedObject.AddValueChanged(TextBlock.TextProperty, TextBlockOnTextChanged);
        AssociatedObject.SizeChanged += AssociatedObjectOnSizeChanged;
    }

    protected override void OnDetaching()
    {
        base.OnDetaching();
        AssociatedObject.RemoveValueChanged(TextBlock.TextProperty, TextBlockOnTextChanged);
        AssociatedObject.SizeChanged -= AssociatedObjectOnSizeChanged;
    }

    private void AssociatedObjectOnSizeChanged(object sender, SizeChangedEventArgs sizeChangedEventArgs)
    {
        CheckToolTipVisibility();
    }

    private void TextBlockOnTextChanged(object sender, EventArgs eventArgs)
    {
        CheckToolTipVisibility();
    }

    private void CheckToolTipVisibility()
    {
        if (AssociatedObject.ActualWidth == 0)
            Dispatcher.BeginInvoke(
                new Action(
                    () => AssociatedObject.ToolTip = CalculateIsTextTrimmed(AssociatedObject) ? _toolTip : null),
                DispatcherPriority.Loaded);
        else
            AssociatedObject.ToolTip = CalculateIsTextTrimmed(AssociatedObject) ? _toolTip : null;
    }

    //Source: https://stackoverflow.com/questions/1041820/how-can-i-determine-if-my-textblock-text-is-being-trimmed
    private static bool CalculateIsTextTrimmed(TextBlock 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) {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 width 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);
    }
}

使用法:

必要な拡張メソッド:

public static class UITools
{
    public static void AddValueChanged(this T obj, DependencyProperty property, EventHandler handler)
        where T : DependencyObject
    {
        var desc = DependencyPropertyDescriptor.FromProperty(property, typeof (T));
        desc.AddValueChanged(obj, handler);
    }

    public static void RemoveValueChanged(this T obj, DependencyProperty property, EventHandler handler)
        where T : DependencyObject
    {
        var desc = DependencyPropertyDescriptor.FromProperty(property, typeof (T));
        desc.RemoveValueChanged(obj, handler);
    }
}

5


「textblock」の「ActualWidth」と「DesiredSize.Width」を比較し、「Visibility」を返すコンバーターを作成できると思います。

2


添付プロパティhttps://stackoverflow.com/a/32655171/3009574 [こちら]を使用して別の回答を投稿しました。これは、コンバーターまたは派生TextBlockコントロールを使用するよりも優れていると思います。

0


タイトルとURLをコピーしました