具有自动换行功能的TextBlock动态字体大小

4

我有一个文本块,它有一个固定的大小并且可以自动换行。有时候文字较短,有时候较长。

如果文本过长,则无法完全显示,就像这样:

enter image description here

如何使字体大小可调整以适应具有静态大小的文本框中的文本?


你可以尝试使用ViewBox,但是在ViewBox内部可能会遇到换行问题。 - odyss-jii
是的,你猜得对,它会破坏包装。不管怎样,谢谢,我还没有遇到过那个元素! - Ostkontentitan
你是否接受使用JavaScript的解决方案? - ic3b3rg
你可以尝试在转换器中将文本分割成相等长度(以像素为单位)的片段,然后使用ViewBox内的ItemsControl查看它们。这不是最美观的解决方案,但可能有效。 - odyss-jii
由于TextRenderer可以在WinForms中实现相同的功能,因此本文可能会帮助您获得另一种解决方案。https://dev59.com/tHRA5IYBdhLWcg3wyRJ7 - Wim Ombelets
我使用了AdaptiveTrigger功能来实现我的简单场景。 - Morten Nørgaard
2个回答

11
我的解决方案如下:
将字体大小设置为您不希望超过的值。当更改字体大小或更改内容时,TextBlock 的 ActualHeight 会发生变化,我基于此构建了解决方案。您应该为 SizeChanged 事件创建一个事件处理程序,并编写以下代码。
private void MyTextBlock_SizeChanged(object sender, SizeChangedEventArgs e)
{
    double desiredHeight = 80; // Here you'll write the height you want the text to use

    if (this.MyTextBlock.ActualHeight > desiredHeight)
    {
        // You want to know, how many times bigger the actual height is, than what you want to have.
        // The reason for Math.Sqrt() is explained below in the text.
        double fontsizeMultiplier = Math.Sqrt(desiredHeight / this.MyTextBlock.ActualHeight);

        // Math.Floor() can be omitted in the next line if you don't want a very tall and narrow TextBox.
        this.MyTextBlock.FontSize = Math.Floor(this.MyTextBlock.FontSize * fontsizeMultiplier);
    }

    this.MyTextBlock.Height = desiredHeight; // ActualHeight will be changed if the text is too big, after the text was resized, but in the end you want the box to be as big as the desiredHeight.
}

我使用Math.Sqrt()的原因是,如果你将字体大小设置为之前的一半,那么字体将使用的区域将是之前的四分之一(因为它变成了之前的一半宽和一半高)。显然,你只想保留TextBox的宽度并改变它的高度。
如果你很幸运,这个方法执行一次后,字体大小就会合适。然而,根据文本在字体大小更改后重新换行的情况,你可能会非常“不幸”,文本会比你想要的多出一行。
幸运的是,事件处理程序将再次被调用(因为你改变了字体大小),如果仍然太大,将再次进行调整。
我尝试过,速度很快,结果看起来很好。但是,我可以想象,在文本和高度的选择非常不幸的情况下,正确的字体大小可能需要多次迭代才能确定。这就是我使用Math.Floor()的原因。总的来说,最终字体大小是12.34还是12并不重要,这样我就不用担心“不幸”的文本需要花费太长时间来渲染。
但是,如果你不想要一个非常高的文本框(比如2000像素)和很多文本,那么可以省略Math.Floor()。

谢谢,这对我很有帮助。请注意:我意识到这不适用于原帖的情况,但在 Windows 10 应用程序中,如果您可以在桌面上调整窗口大小,则会导致文本缩小但无法再次变大。 - tagy22
@tagy22,删掉if语句就可以实现双向调整大小。 - Alkasai

0
这是一个完整的解决方案,包括设置最大高度/宽度选项,并且可以在呈现时直接计算:
public class TextBlockAutoShrink : TextBlock
    {
        private double _defaultMargin = 6;
        private Typeface _typeface;

        static TextBlockAutoShrink()
        {
            TextBlock.TextProperty.OverrideMetadata(typeof(TextBlockAutoShrink), new FrameworkPropertyMetadata(new PropertyChangedCallback(TextPropertyChanged)));
        }

        public TextBlockAutoShrink() : base() 
        {
            _typeface = new Typeface(this.FontFamily, this.FontStyle, this.FontWeight, this.FontStretch, this.FontFamily);
            base.DataContextChanged += new DependencyPropertyChangedEventHandler(TextBlockAutoShrink_DataContextChanged);
        }

        private static void TextPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
        {
            var t = sender as TextBlockAutoShrink;
            if (t != null)
            {
                t.FitSize();
            }
        }

        void TextBlockAutoShrink_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
        {
            FitSize();
        }

        protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
        {
            FitSize();

            base.OnRenderSizeChanged(sizeInfo);
        }


        private void FitSize()
        {
            FrameworkElement parent = this.Parent as FrameworkElement;
            if (parent != null)
            {
                var targetWidthSize = this.FontSize;
                var targetHeightSize = this.FontSize;

                var maxWidth = double.IsInfinity(this.MaxWidth) ? parent.ActualWidth : this.MaxWidth;
                var maxHeight = double.IsInfinity(this.MaxHeight) ? parent.ActualHeight : this.MaxHeight;

                if (this.ActualWidth > maxWidth)
                {
                    targetWidthSize = (double)(this.FontSize * (maxWidth / (this.ActualWidth + _defaultMargin)));
                }

                if (this.ActualHeight > maxHeight)
                {
                    var ratio = maxHeight / (this.ActualHeight);

                    // Normalize due to Height miscalculation. We do it step by step repeatedly until the requested height is reached. Once the fontsize is changed, this event is re-raised
                    // And the ActualHeight is lowered a bit more until it doesnt enter the enclosing If block.
                    ratio = (1 - ratio > 0.04) ? Math.Sqrt(ratio) : ratio;

                    targetHeightSize = (double)(this.FontSize *  ratio);
                }

                this.FontSize = Math.Min(targetWidthSize, targetHeightSize);
            }
        }
    }

1
_typeface 在你的示例中没有被使用。 - canton7

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接