浮华过后,真金始现

一切问题最终都是时间问题,一切烦恼其实都是自寻烦恼
随笔 - 20, 文章 - 10, 评论 - 92, 阅读 - 83873
  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理
< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5

[WPF/SL]TextBlock变形记(TextTrimming)

Posted on   Kolor  阅读(4996)  评论(1编辑  收藏  举报

再灵活的布局也不是万能的,极端的情况总会让它的脆弱一览无余,比如今天的主角TextBlock,它在WPF/SL应用程序中担任着显示文本的职责,它即轻巧又强大,大部分情况下都工作的非常好,但是过长的文本就会出现字符串截断了,这显然不是我们期望的,幸运的是,MS已经考虑到了这点,通过设置TextBlock的TextTrimming属性可以改变这种现象,可选值分别为None, CharacterEllipsis和WordEllipsis,字面意思已经很清楚了,就不再翻译了吧,对于非None值,会在截断处自动添加"...", 用户体验上自然更上了一层楼。

但是如果这部分文字比较重要,又比较长,如果仅是达到以上显示效果,是不是会令人抓狂呢(是的,你非常友好的告诉我这段信息太长,被截断了,但是你总得有个途径让我看到完整的信息吧)

为此EllipseBehavior应运而生,能够使得TextBlock在出现字符截断时自动将完整信息以ToolTip的形式展示出来,是不是很酷,但它的代价却是那么的微乎其微,你只需要添加类似如下的代码即可:

UI代码
<TextBlock Name="tb" Margin="50"  Text="Browse XAML, WPF, and Behavior" Height="48" VerticalAlignment="Top">
    
<interact:Interaction.Behaviors>
        
<Behaviors:EllipseBehavior/>
    
</interact:Interaction.Behaviors>
</TextBlock>

 

 是不是很方便呢?由此我们可以看出Behavior确实给予了我们很大的方便

下面给出EllipseBehavior的代码

复制代码
C#代码
using System.ComponentModel;
using System.Reflection;
using System.Windows.Controls;
using System.Windows.Threading;

namespace System.Windows.Behaviors
{
    
/// <summary>
    
/// Enable TextBlock show tooltip with full text when ellipsing
    
/// </summary>
    public class EllipseBehavior : System.Windows.Interactivity.Behavior<TextBlock>
    {
        
#region Field

        MethodInfo m_GetLineDetails = typeof(TextBlock).GetMethod("GetLineDetails",
              BindingFlags.NonPublic | BindingFlags.Instance);
        
object[] args = new object[] { 00000 };
        
#endregion

        
#region DependencyProperty

        
/// <summary>
        
/// Gets or sets a value indicating whether [auto show tooltip when ellipsed].
        
/// </summary>
        
/// <value>
        
///     <c>true</c> if [auto show tooltip when ellipsed]; otherwise, <c>false</c>.
        
/// </value>
        public bool AutoShowTooltipWhenEllipsed
        {
            
get { return (bool)GetValue(AutoShowTooltipWhenEllipsedProperty); }
            
set { SetValue(AutoShowTooltipWhenEllipsedProperty, value); }
        }
        
// Using a DependencyProperty as the backing store for IsShowTooltipWhenEllipsed.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty AutoShowTooltipWhenEllipsedProperty =
            DependencyProperty.Register("AutoShowTooltipWhenEllipsed"typeof(bool), typeof(EllipseBehavior), new UIPropertyMetadata(true));

        
#endregion

        
#region Override

        
/// <summary>
        
/// Called after the behavior is attached to an AssociatedObject.
        
/// </summary>
        
/// <remarks>Override this to hook up functionality to the AssociatedObject.</remarks>
        protected override void OnAttached()
        {
            
base.OnAttached();

            
if (AutoShowTooltipWhenEllipsed && this.AssociatedObject.TextTrimming == TextTrimming.None)
                
this.AssociatedObject.TextTrimming = TextTrimming.CharacterEllipsis;
            
//Register events
            var dpDesc = DependencyPropertyDescriptor.FromProperty(TextBlock.TextProperty, typeof(TextBlock));
            dpDesc.AddValueChanged(this.AssociatedObject, OnTextChanged);
            
this.AssociatedObject.Loaded += new RoutedEventHandler(OnLoaded);
        }

        
/// <summary>
        
/// Called when the behavior is being detached from its AssociatedObject, but before it has actually occurred.
        
/// </summary>
        
/// <remarks>Override this to unhook functionality from the AssociatedObject.</remarks>
        protected override void OnDetaching()
        {
            
base.OnDetaching();
            
//unload events
            this.AssociatedObject.Loaded -= new RoutedEventHandler(OnLoaded);
            
this.AssociatedObject.SizeChanged -= new SizeChangedEventHandler(OnSizeChanged);
            var dpDesc = DependencyPropertyDescriptor.FromProperty(TextBlock.TextProperty, typeof(TextBlock));
            dpDesc.RemoveValueChanged(this.AssociatedObject, OnTextChanged);
        }

        
#endregion

        
#region EventHandler

        
/// <summary>
        
/// Called when [size changed].
        
/// </summary>
        
/// <param name="sender">The sender.</param>
        
/// <param name="e">The <see cref="System.Windows.SizeChangedEventArgs"/> instance containing the event data.</param>
        void OnSizeChanged(object sender, SizeChangedEventArgs e)
        {
            SetToolTip(null);
        }

        
/// <summary>
        
/// Called when AssociatedObject [loaded].
        
/// </summary>
        
/// <param name="sender">The sender.</param>
        
/// <param name="e">The <see cref="System.Windows.RoutedEventArgs"/> instance containing the event data.</param>
        void OnLoaded(object sender, RoutedEventArgs e)
        {
            SetToolTip(null);
            
//Register SizeChanged event
            this.AssociatedObject.SizeChanged += new SizeChangedEventHandler(OnSizeChanged);
        }

        
/// <summary>
        
/// Overrides the <see cref="T:System.Windows.DependencyObject"/> implementation of <see cref="M:System.Windows.DependencyObject.OnPropertyChanged(System.Windows.DependencyPropertyChangedEventArgs)"/> to also invoke any <see cref="E:System.Windows.Freezable.Changed"/> handlers in response to a changing dependency property of type <see cref="T:System.Windows.Freezable"/>.
        
/// </summary>
        
/// <param name="e">Event data that contains information about which property changed, and its old and new values.</param>
        protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
        {
            
base.OnPropertyChanged(e);
            
if (this.AssociatedObject != null && e.Property == AutoShowTooltipWhenEllipsedProperty)
            {
                
if ((bool)e.NewValue && this.AssociatedObject.TextTrimming == TextTrimming.None)
                    
this.AssociatedObject.TextTrimming = TextTrimming.CharacterEllipsis;
                
// if AssociatedObject is loaded, update tooltip
                if (this.AssociatedObject.IsLoaded)
                    SetToolTip(null);
            }
        }

        
/// <summary>
        
/// Called when AssociatedObject [text changed].
        
/// </summary>
        
/// <param name="sender">The sender.</param>
        
/// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void OnTextChanged(object sender, EventArgs e)
        {
            Dispatcher.Invoke(DispatcherPriority.Background, (Action<object>)SetToolTip, null);
        }
        
#endregion

        
#region Method

        
/// <summary>
        
/// Sets the tool tip.
        
/// </summary>
        
/// <param name="obj">The obj.</param>
        private void SetToolTip(object obj)
        {
            
if (AutoShowTooltipWhenEllipsed && this.AssociatedObject.TextTrimming != TextTrimming.None)
            {
                TextBlock tb = this.AssociatedObject;
                m_GetLineDetails.Invoke(tb, args);
                
if ((int)args[4> 0)
                {
                    tb.ToolTip = tb.Text;
                }
                
else
                {
                    tb.ToolTip = null;
                }
            }
        }
        
#endregion
    }
}
复制代码

 

 GetLineDetails方法的访问限定是internal,此处只能通过反射调用

internal void GetLineDetails(int dcp, int index, double lineVOffset, 
   
out int cchContent, out int cchEllipses)
{
  ...
}

 

调用后,其最后一个参数cchEllipses的值0与非0正好可以标识此时TextBlock是否发生ellipsing

需要注意的是 GetLineDetails方法必须在控件Load之后才能使用,否则会抛异常

 

 

编辑推荐:
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
阅读排行:
· 周边上新:园子的第一款马克杯温暖上架
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· DeepSeek如何颠覆传统软件测试?测试工程师会被淘汰吗?
· 使用C#创建一个MCP客户端
点击右上角即可分享
微信分享提示