テキストをラップする固定サイズの TextBlock があります。時には短く、時には長く。
テキストが長くなると、このように完全に表示されません
Fontsize を柔軟にして、テキストを静的サイズの TextBox に合わせるにはどうすればよいですか?
私の解決策は次のとおりです。
フォントサイズをこれ以上大きくしたくない値に設定します。フォント サイズを変更するか、コンテンツを変更すると、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() を使用した理由は、フォント サイズを以前の半分に設定すると、フォントが使用する領域が以前の 4 分の 1 のサイズになるためです (半分になったため)。幅は以前の半分で、高さは半分です)。そして、明らかに TextBox の幅を維持し、高さだけを変更したいとします。
運が良ければ、このメソッドが 1 回実行されると、適切なフォント サイズになります。ただし、フォント サイズの変更後に再ラップされるテキストによっては、「運が悪い」ため、テキストが希望よりも 1 行長くなる場合があります。幸いなことに、(フォント サイズを変更したため) イベント ハンドラーが再度呼び出され、それでも大きすぎる場合はサイズ変更が再度行われます。
私はそれを試してみましたが、それは速く、結果はよさそうでした。ただし、テキストと高さの非常に不運な選択では、数回の反復後に正しいフォントサイズに到達することは想像できます. これが、Math.Floor() を使用した理由です。全体として、フォント サイズが 12.34 か 12 のどちらであるかは大した問題ではありません。このようにして、レンダリングに時間がかかりすぎる「不運な」テキストについて心配する必要はありません。しかし、大量のテキストを含む非常に高いテキスト ボックス (2000 ピクセルなど) を使用したくない場合は、 Math.Floor() を省略できると思います。
これは、maxheight / maxwidth を設定するオプションを含む完全なソリューションであり、レンダリング時に直接計算されます。
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);
}
}
}