Sunday, July 12, 2009

TextBox with automatically resizing of the fontsize

View the demo 

Download the source

 

A while back I ran into this textbox that automatically made the font smaller when the text was larger than the size of the textbox.

I wanted to recreate one in Silverlight.

 

The textbox had to have a property for the minimum size of the font. We don’t want our users to see a textbox with a font of 0.25 pixels. That wouldn’t be a good user experience :)

The rest of the stuff has to go pretty much automatically. When the user types, the fontsize automatically adjust. It gets smaller when the text is getting larger than the available space in the textbox and the fontsize gets larger when there’s enough available space.

 

Create a control

In Visual Studio, add a class and make it inherit the default TextBox:

public class TextBoxAutoResizingText : TextBox 

I am going to use a TextBlock to get the width of the text so that we can compare it to the ActualWidth property of the TextBox. If anyone has a different suggestion of how to achieve this, please let me know!

First of all, we need to create a field to keep the value of the original FontSize in. We create this field as a nullable double so we can check if it’s set, and only set it once:

private readonly TextBlock _textBlock = new TextBlock();  
private double? _originalFontSize;

Besides these private fields we have a few properties we can use to set the values of the: MinFontSize, Offset and Step. The Offset is required to prevent the text from dissapearing on the left side of the TextBox. The Step property is used as the amount in which the font needs to be scaled up or down. By default this is set to .25:

private double _minFontSize = 7;
public double MinFontSize
{
     get { return _minFontSize; }
     set { _minFontSize = value; }
}
private double _step = .25;
public double Step
{
     get { return _step; }
     set { _step = value; }
}
private double _offset = 12;
public double Offset
{
     get { return _offset; }
     set { _offset = value; }
}

Hooking up the events

We have a couple of events we’re going to use for this control. First of all we’re using the LayoutUpdated event to retrieve the original value of the fontsize and create our TextBlock so that it has almost the same properties as the TextBox.

In the constructor, we hook up the two events we’re using. That leaves us with the following code:

public TextBoxAutoResizingText()
{
     TextChanged += TextBoxAutoResizingTextTextChanged;
     LayoutUpdated += TextBoxAutoResizingTextLayoutUpdated;
}
private void TextBoxAutoResizingTextLayoutUpdated(object sender, EventArgs e)
{
     _textBlock.FontFamily = FontFamily;
     _textBlock.FontSize = FontSize;
     _textBlock.Padding = Padding;
     if(_originalFontSize == null)
     {
         _originalFontSize = FontSize;
     }
}

As you can see, we check if the _originalFontSize is set so it won’t be set every time the event is triggered.


The TextChanged event

This is the event where the actual work is done.
We can separate the event in two parts, one piece occurs when the text is larger than the available space in the TextBox and the other part when text is smaller than the available space:

private void TextBoxAutoResizingTextTextChanged(object sender, TextChangedEventArgs e)
{
     _textBlock.Text = Text;
     if (_textBlock.ActualWidth > ActualWidth - BorderThickness.Left - BorderThickness.Right - _offset)
     {
         while (_textBlock.ActualWidth > ActualWidth - BorderThickness.Left - BorderThickness.Right - _offset && FontSize > MinFontSize)
         {
             if (FontSize - _step <= MinFontSize)
                 break;
             FontSize -= _step;
             _textBlock.FontSize = FontSize;
         }
     }
     else
     {
         while (_textBlock.ActualWidth < ActualWidth - BorderThickness.Left - BorderThickness.Right - _offset && FontSize < _originalFontSize)
         {
             if (FontSize + _step <= MinFontSize)
                 break;
             FontSize += _step;
             _textBlock.FontSize = FontSize;
         }
     }
}

And that’s all there is to it. Pretty simple solution, but it can enhance the user experience without much effort.


This control can also be used from within Expression Blend. Because we used properties for the MinFontSize, it is easily adjustable in the Blend toolbox “Miscellaneous”.

View the demo 

Download the source

3 comments:

  1. Interesting article Rob. I guess this mechanism would work with TextBlock as well. Also what about resizing the TextBox, if I make it larger it won't update the font untill Text has changed. Maybe that's a nice addition, if size change resize font as well.

    ReplyDelete