Maybe a TextBlock
would be better for presenting text-for-printing than a TextBox
?
I tried implementing Jonah's solution. Inspired by This answer I wrapped it in a behavior (also note the addition of formattedText.Trimming = TextTrimming.None
)
using System.Collections.Generic;
using System.ComponentModel;
using System.Threading;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Interactivity;
using System.Windows.Media;
namespace WpfApplication1
{
public class ScaleFontBehavior : Behavior<TextBlock>
{
// MaxFontSize
public static readonly DependencyProperty MaxFontSizeProperty = DependencyProperty.Register("MaxFontSize",
typeof (double), typeof (ScaleFontBehavior), new PropertyMetadata(20d));
// MinFontSize
public static readonly DependencyProperty MinFontSizeProperty = DependencyProperty.Register("MinFontSize",
typeof (double), typeof (ScaleFontBehavior), new PropertyMetadata(12d));
private readonly TextBlock dummy = new TextBlock();
public double MaxFontSize
{
get { return (double) GetValue(MaxFontSizeProperty); }
set { SetValue(MaxFontSizeProperty, value); }
}
public double MinFontSize
{
get { return (double) GetValue(MinFontSizeProperty); }
set { SetValue(MinFontSizeProperty, value); }
}
protected override void OnAttached()
{
DependencyPropertyDescriptor dpd = DependencyPropertyDescriptor.FromProperty(TextBlock.TextProperty,
typeof (TextBlock));
if (dpd != null)
{
dpd.AddValueChanged(AssociatedObject, delegate { CalculateFontSize(); });
}
AssociatedObject.SizeChanged += (s, e) => CalculateFontSize();
dummy.MaxWidth = AssociatedObject.MaxWidth;
dummy.TextWrapping = AssociatedObject.TextWrapping;
}
private void CalculateFontSize()
{
double fontSize = MaxFontSize;
AssociatedObject.FontSize = fontSize;
while (CalculateIsTextTrimmed(AssociatedObject))
{
fontSize--;
if (fontSize < MinFontSize) break;
AssociatedObject.FontSize = fontSize;
}
}
private static bool CalculateIsTextTrimmed(TextBlock textBlock)
{
var typeface = new Typeface(
textBlock.FontFamily,
textBlock.FontStyle,
textBlock.FontWeight,
textBlock.FontStretch);
var formattedText = new FormattedText(
textBlock.Text,
Thread.CurrentThread.CurrentCulture,
textBlock.FlowDirection,
typeface,
textBlock.FontSize,
textBlock.Foreground)
{ Trimming = TextTrimming.None };
formattedText.MaxTextWidth = textBlock.Width;
return (formattedText.Height > textBlock.Height);
}
}
class VisualHelper
{
public static List<T> FindVisualChildren<T>(DependencyObject obj) where T : DependencyObject
{
List<T> children = new List<T>();
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
{
var o = VisualTreeHelper.GetChild(obj, i);
if (o != null)
{
if (o is T)
children.Add((T)o);
children.AddRange(FindVisualChildren<T>(o)); // recursive
}
}
return children;
}
public static T FindUpVisualTree<T>(DependencyObject initial) where T : DependencyObject
{
DependencyObject current = initial;
while (current != null && current.GetType() != typeof(T))
{
current = VisualTreeHelper.GetParent(current);
}
return current as T;
}
}
}
Here's is an example of it's usage (type into the TextBox
and see the text in the TextBlock
scale to fit.
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:wpfApplication1="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="525">
<StackPanel>
<TextBox x:Name="TextBox" MaxLines="10" AcceptsReturn="True" />
<TextBlock Text="{Binding ElementName=TextBox, Path=Text}" Height="200" Width="200" TextWrapping="Wrap">
<i:Interaction.Behaviors>
<wpfApplication1:ScaleFontBehavior MaxFontSize="50" MinFontSize="12" />
</i:Interaction.Behaviors>
</TextBlock>
</StackPanel>
</Window>