浮华过后,真金始现

一切问题最终都是时间问题,一切烦恼其实都是自寻烦恼
  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

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

Posted on 2009-12-04 22:22  Kolor  阅读(4981)  评论(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之后才能使用,否则会抛异常