如何计算TextEffect实际尺寸并修正NewTextBlock的实际宽度?
自定义TextBlock实现LetterSpacing后控件尺寸未包含TextEffect宽度的解决方法
为实现LetterSpacing功能,我自定义了继承自TextBlock的NewTextBlock控件,代码如下:
public class NewTextBlock : TextBlock { public NewTextBlock() : base() { var dp = DependencyPropertyDescriptor.FromProperty( TextBlock.TextProperty, typeof(TextBlock)); dp.AddValueChanged(this, (sender, args) => { LetterSpacingChanged(this); }); } static void LetterSpacingChanged(DependencyObject d) { NewTextBlock TB = d as NewTextBlock; TB.TextEffects.Clear(); for (int i = 1; i <= TB.Text.Length; i++) { TranslateTransform transform = new TranslateTransform(TB.LetterSpacing, 0); TextEffect effect = new TextEffect(); effect.Transform = transform; effect.PositionStart = i; effect.PositionCount = TB.Text.Length; TB.TextEffects.Add(effect); if (effect.CanFreeze) { effect.Freeze(); } } } public double LetterSpacing { get { return (double)GetValue(LetterSpacingProperty); } set { SetValue(LetterSpacingProperty, value); } } // Using a DependencyProperty as the backing store for LetterSpacing. This enables animation, styling, binding, etc... public static readonly DependencyProperty LetterSpacingProperty = DependencyProperty.Register("LetterSpacing", typeof(double), typeof(NewTextBlock), new FrameworkPropertyMetadata(0.0, new PropertyChangedCallback(LetterSpacingChanged))); private static void LetterSpacingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { LetterSpacingChanged(d); } }
使用该控件时:
<Test:NewTextBlock Background="Red" HorizontalAlignment="Center" VerticalAlignment="Center" LetterSpacing="5" Text="123123123123"> <Test:NewTextBlock.LayoutTransform> <RotateTransform Angle="45"></RotateTransform> </Test:NewTextBlock.LayoutTransform> </Test:NewTextBlock>
发现NewTextBlock的实际宽度并未包含TextEffect的宽度(红色背景未覆盖完整变换后的文本),需要获取TextEffect的实际尺寸并设置正确的控件宽度。
解决方案
TextEffect属于视觉渲染层的效果,不会参与控件的布局测量逻辑,所以默认情况下控件的DesiredSize只基于原始文本的宽度。要让控件尺寸包含LetterSpacing带来的额外宽度,需要重写MeasureOverride方法,手动计算正确的所需尺寸:
修改后的NewTextBlock代码
public class NewTextBlock : TextBlock { public NewTextBlock() : base() { var dp = DependencyPropertyDescriptor.FromProperty( TextBlock.TextProperty, typeof(TextBlock)); dp.AddValueChanged(this, (sender, args) => { LetterSpacingChanged(this); InvalidateMeasure(); // 文本变化时重新触发测量 }); } static void LetterSpacingChanged(DependencyObject d) { NewTextBlock TB = d as NewTextBlock; TB.TextEffects.Clear(); for (int i = 1; i <= TB.Text.Length; i++) { TranslateTransform transform = new TranslateTransform(TB.LetterSpacing, 0); TextEffect effect = new TextEffect(); effect.Transform = transform; effect.PositionStart = i; effect.PositionCount = TB.Text.Length; TB.TextEffects.Add(effect); if (effect.CanFreeze) { effect.Freeze(); } } } public double LetterSpacing { get { return (double)GetValue(LetterSpacingProperty); } set { SetValue(LetterSpacingProperty, value); } } public static readonly DependencyProperty LetterSpacingProperty = DependencyProperty.Register("LetterSpacing", typeof(double), typeof(NewTextBlock), new FrameworkPropertyMetadata(0.0, new PropertyChangedCallback(LetterSpacingChanged))); private static void LetterSpacingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { LetterSpacingChanged(d); (d as NewTextBlock).InvalidateMeasure(); // 间距变化时重新触发测量 } // 重写测量方法,计算包含LetterSpacing的正确尺寸 protected override Size MeasureOverride(Size constraint) { // 先获取原始TextBlock的测量尺寸 Size originalSize = base.MeasureOverride(constraint); // 计算LetterSpacing带来的额外宽度:n个字符有n-1个间距 double extraWidth = LetterSpacing * Math.Max(0, Text.Length - 1); // 返回新的尺寸,宽度加上额外间距,高度保持不变 return new Size(originalSize.Width + extraWidth, originalSize.Height); } }
关键说明
- InvalidateMeasure():在Text或LetterSpacing属性变化时调用此方法,强制控件重新执行布局测量,确保尺寸更新。
- MeasureOverride重写:
- 先调用
base.MeasureOverride获取原始文本的尺寸; - 额外宽度计算为
LetterSpacing * (文本长度-1),因为n个字符之间有n-1个间隔; - 返回叠加后的尺寸,让控件的布局边界包含所有变换后的文本。
- 先调用
- LayoutTransform兼容:LayoutTransform会在控件测量完成后应用,所以不需要在MeasureOverride中处理变换,WPF会自动基于测量后的尺寸进行变换渲染。
这样修改后,NewTextBlock的红色背景就会完整覆盖包含LetterSpacing的所有文本区域了。
内容的提问来源于stack exchange,提问作者Volfogg
相关产品推荐
相关产品推荐

