You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何计算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);
    }
}

关键说明

  1. InvalidateMeasure():在Text或LetterSpacing属性变化时调用此方法,强制控件重新执行布局测量,确保尺寸更新。
  2. MeasureOverride重写:
    • 先调用base.MeasureOverride获取原始文本的尺寸;
    • 额外宽度计算为LetterSpacing * (文本长度-1),因为n个字符之间有n-1个间隔;
    • 返回叠加后的尺寸,让控件的布局边界包含所有变换后的文本。
  3. LayoutTransform兼容:LayoutTransform会在控件测量完成后应用,所以不需要在MeasureOverride中处理变换,WPF会自动基于测量后的尺寸进行变换渲染。

这样修改后,NewTextBlock的红色背景就会完整覆盖包含LetterSpacing的所有文本区域了。


内容的提问来源于stack exchange,提问作者Volfogg

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.01 04:55:33