再灵活的布局也不是万能的,极端的情况总会让它的脆弱一览无余,比如今天的主角TextBlock,它在WPF/SL应用程序中担任着显示文本的职责,它即轻巧又强大,大部分情况下都工作的非常好,但是过长的文本就会出现字符串截断了,这显然不是我们期望的,幸运的是,MS已经考虑到了这点,通过设置TextBlock的TextTrimming属性可以改变这种现象,可选值分别为None, CharacterEllipsis和WordEllipsis,字面意思已经很清楚了,就不再翻译了吧,对于非None值,会在截断处自动添加"...", 用户体验上自然更上了一层楼。
但是如果这部分文字比较重要,又比较长,如果仅是达到以上显示效果,是不是会令人抓狂呢(是的,你非常友好的告诉我这段信息太长,被截断了,但是你总得有个途径让我看到完整的信息吧)
为此EllipseBehavior应运而生,能够使得TextBlock在出现字符截断时自动将完整信息以ToolTip的形式展示出来,是不是很酷,但它的代价却是那么的微乎其微,你只需要添加类似如下的代码即可:
<interact:Interaction.Behaviors>
<Behaviors:EllipseBehavior/>
</interact:Interaction.Behaviors>
</TextBlock>
是不是很方便呢?由此我们可以看出Behavior确实给予了我们很大的方便
下面给出EllipseBehavior的代码
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[] { 0, 0, 0, 0, 0 };
#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,此处只能通过反射调用
out int cchContent, out int cchEllipses)
{
...
}
调用后,其最后一个参数cchEllipses的值0与非0正好可以标识此时TextBlock是否发生ellipsing
需要注意的是 GetLineDetails方法必须在控件Load之后才能使用,否则会抛异常