2

I have several blocks of text that I need to add to a canvas for printing. They will all have a set width and height. They are all multiline and I would like the font to scale as large as possible. I have tried several things with a viewbox, however I can't seem to get the multiline and font scaling to work in unison.

<Viewbox Width="200" Height="200" Stretch="Uniform" >
        <TextBox Text="Test test test test test test Test test test test test test " 
                 TextWrapping="Wrap" AcceptsReturn="True" BorderThickness="0"></TextBox>

  </Viewbox>

If I have somelike like the above, I get one line of text.

Jonah Kunz
  • 670
  • 8
  • 19
  • when you increase size of viewbox, the font grows. What do you mean doesn't work? – voddy Apr 11 '14 at 01:22
  • The text will grow in a single line because it has no width bounds. so I will end up with 1 tiny line of text rather than multiline. – Jonah Kunz Apr 11 '14 at 01:24
  • It may not be answer but can you try the code sample I just posted and let me know what's your requirement. Then it's easy for me to try out – voddy Apr 11 '14 at 01:27

3 Answers3

1

The only way I could see to do this was to start with a large font and scale it down until it fits.

    double fontSize = 30;
    tb.FontSize = fontSize;
    while (CalculateIsTextTrimmed(tb))
    {
      fontSize--;
      tb.FontSize = fontSize;
    }

private static bool CalculateIsTextTrimmed(TextBlock textBlock)
{
  Typeface typeface = new Typeface(
      textBlock.FontFamily,
      textBlock.FontStyle,
      textBlock.FontWeight,
      textBlock.FontStretch);

  FormattedText formattedText = new FormattedText(
      textBlock.Text,
      System.Threading.Thread.CurrentThread.CurrentCulture,
      textBlock.FlowDirection,
      typeface,
      textBlock.FontSize,
      textBlock.Foreground);

  formattedText.MaxTextWidth = textBlock.Width;
  return (formattedText.Height > textBlock.Height);
}
Jonah Kunz
  • 670
  • 8
  • 19
1

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>
Community
  • 1
  • 1
jdpilgrim
  • 358
  • 3
  • 13
0

check if this works:

  <Canvas>
    <Viewbox Width="200" Height="200" Stretch="Uniform" >
        <StackPanel Width="200" Height="200" >
            <TextBox Width="Auto" Text="Test test test test test " TextWrapping="Wrap" AcceptsReturn="True"  ></TextBox>
            <TextBox Width="Auto" Text="Test test test test test " TextWrapping="Wrap" AcceptsReturn="True"  ></TextBox>
            <TextBlock  Width="Auto" Text="Test test test test test " TextWrapping="Wrap"  ></TextBlock>
        </StackPanel>
    </Viewbox>
 </Canvas>
voddy
  • 950
  • 1
  • 11
  • 21
  • I don't want to set a width on the textblock or textbox itself because I don't want to limit how large it can grow. – Jonah Kunz Apr 11 '14 at 01:39
  • Then set the width to Auto. Width="Auto". Would that work? But then wrapping wouldn't work..humm – voddy Apr 11 '14 at 01:42
  • Nope, still only 1 line of text there. – Jonah Kunz Apr 11 '14 at 01:44
  • Specifying a width will not stop it from growing. When viewbox is re-sized, the textblock grows too – voddy Apr 11 '14 at 01:54
  • In my example above, my viewbox width and height are both 200. If that is the case, how would I know what width to put my textbox at to scale it to fill the viewbox? – Jonah Kunz Apr 11 '14 at 01:56
  • You can add a StackPanel and set its width & height the same as ViewBox. And add your TextBlock or TextBox inside stackpanel and set width to Auto. I updated my code – voddy Apr 11 '14 at 02:03
  • Doing that will not stretch the font to its maximum size, I would still have to figure out what font size to use. – Jonah Kunz Apr 11 '14 at 02:07