wpf 如何计算文本效果的实际大小?

dphi5xsq  于 2023-02-13  发布在  其他
关注(0)|答案(3)|浏览(231)

我做了一个自定义控件继承TextBlock的LetterSpacing,这里是我的代码:

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>

原来是这样的:enter image description here
如红色背景所示,NewTextBlock的实际宽度似乎不包括TextEffect的宽度。
我想找到一种方法来获得文本效果的实际宽度,并为NewTextBlock设置一个正确的实际宽度。
我该怎么做呢?

0wi1tuuw

0wi1tuuw1#

不要使用DependencyPropertyDescriptor来注册一个属性改变的回调。如果你不小心(你的代码就是这种情况),它将创建一个(潜在的严重)每个示例内存泄漏。
最好在static构造函数中重写dependency属性的元数据,以注册新的回调(参见下面的示例)。
TextBlock不支持自定义字符间距。TextBlock完全依赖提供的字体(或字样)来计算实际文本宽度。由于MeasureOverride是密封的,因此在更改Text值或任何与文本相关的属性(例如FontSize等)后,通常必须强制使用新的Width
这意味着,要正确实现大小调整行为,您必须重写所有相关属性的属性元数据,以触发实际NewTextBlock宽度的重新计算。您可以遵循以下示例中TextBlock.TextProperty属性的元数据重写。
要计算格式化字符串的宽度,通常使用FormattedText类(见下文),在这种情况下,必须将当前字符间距包括在计算中。

public class NewTextBlock : TextBlock
{
  public double LetterSpacing
  {
    get => (double)GetValue(LetterSpacingProperty);
    set => SetValue(LetterSpacingProperty, value);
  }

  public static readonly DependencyProperty LetterSpacingProperty = DependencyProperty.Register(
    "LetterSpacing", 
    typeof(double), 
    typeof(NewTextBlock), 
    new FrameworkPropertyMetadata(default(double), OnLetterSpacingChanged));

  static NewTextBlock() 
  {
    TextProperty.OverrideMetadata(
      typeof(NewTextBlock), 
      new FrameworkPropertyMetadata(default(string), OnTextChanged));
  }

  public NewTextBlock() 
  {
    this.TextEffects = new TextEffectCollection();
  }

  private static void OnTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    => (d as NewTextBlock).ApplyTextEffects();

  private static void OnLetterSpacingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) 
    => (d as NewTextBlock).ApplyTextEffects();

  private void ApplyTextEffects()
  {
    if (string.IsNullOrWhiteSpace(this.Text))
    {
      return;
    }

    this.TextEffects.Clear();
    for (int characterIndex = 1; characterIndex <= this.Text.Length; characterIndex++)
    {
      var transform = new TranslateTransform(this.LetterSpacing, 0);
      var effect = new TextEffect
      {
        Transform = transform,
        PositionStart = characterIndex,
        PositionCount = this.Text.Length
      };

      this.TextEffects.Add(effect);
      if (effect.CanFreeze)
      {
        effect.Freeze();
      }
    }

    AdjustCurrentWidth();
  }

  private void AdjustCurrentWidth()
  {
    var typeface = new Typeface(this.FontFamily, this.FontStyle, this.FontWeight, this.FontStretch);
    double pixelsPerDip = VisualTreeHelper.GetDpi(this).PixelsPerDip;
    var formattedText = new FormattedText(this.Text, CultureInfo.CurrentCulture, FlowDirection.LeftToRight, typeface, this.FontSize, this.Foreground, pixelsPerDip);
    double totalLetterSpacingWidth = (this.Text.Length - 1) * this.LetterSpacing;
    double totalTextWidth = formattedText.WidthIncludingTrailingWhitespace + totalLetterSpacingWidth;

    this.Width = totalTextWidth;
  }
}
bkhjykvo

bkhjykvo2#

也许我错过了这个STextBlock的要点,但在我看来,您可以(也许应该)只使用Glyphs框架元素来代替。
https://learn.microsoft.com/en-us/dotnet/desktop/wpf/advanced/introduction-to-the-glyphrun-object-and-glyphs-element?view=netframeworkdesktop-4.8
https://learn.microsoft.com/en-us/dotnet/api/system.windows.documents.glyphs?view=windowsdesktop-7.0
乙二醇

<!-- "Hello World!" with explicit character widths for proportional font -->
<Glyphs 
   FontUri             = "C:\WINDOWS\Fonts\ARIAL.TTF"
   FontRenderingEmSize = "36"
   UnicodeString       = "Hello World!"
   Indices             = ",80;,80;,80;,80;,80;,80;,80;,80;,80;,80;,80"
   Fill                = "Maroon"
   OriginX             = "50"
   OriginY             = "225"
/>

<!-- "Hello World!" with fixed-width font -->
<Glyphs 
   FontUri             = "C:\WINDOWS\Fonts\COUR.TTF"
   FontRenderingEmSize = "36"
   StyleSimulations    = "BoldSimulation"
   UnicodeString       = "Hello World!"
   Fill                = "Maroon"
   OriginX             = "50"
   OriginY             = "300"
/>
jmo0nnb3

jmo0nnb33#

我从@BionicCode的代码中找到了另一个解决方案

protected override void OnRender(DrawingContext drawingContext)
        {
            base.OnRender(drawingContext);

            if (!string.IsNullOrEmpty(Text))
            {                
                double PositionX = Padding.Left;
                double PositionY = Padding.Top;
                foreach (var i in Text)
                {
                    var FT = new FormattedText(i.ToString(), CultureInfo.CurrentUICulture, FlowDirection.LeftToRight, new Typeface(FontFamily,FontStyle,FontWeight,FontStretch), FontSize, Foreground);                    
                    drawingContext.DrawText(FT, new Point(PositionX, PositionY));
                    PositionX += FT.Width+LetterSpacing;
                    var t = Width;                    
                }
            }
        }

相关问题