0
votes

I have a WPF application, that has a number TextBox elements. The user fills those out, and the application then prints them. The problem with TextBoxes is that if you keep typing in one, once you fill it out to the end, the text starts scrolling horizontally to make room for more letters, and you no longer see the first couple letters that were typed in.

It was decided that the solution is to prevent the user from entering more text that will fit inside the TextBox. What's the best way of going about it?

I looked at TextBox properties, and didn't see anything that would do what I want directly. The first idea, is to set wrapping to Wrap. Then subscribe to PreviewTextInput event, and if the number of lines is going to exceed 1, handle the event without adding newly typed in text. Obviously you'll still be able to get around it by pasting text, but the bigger issue is that it will only work for single line TextBoxes, and I need it to work with multiline TextBoxes as well.

Is there a better approach that I'm missing? Would calculating text width, and then making sure it's less than TextBox width/height (how?) be a better option? Or perhaps another solution?

2
I think you just have to put it in a Grid and it will auto size to fit the area, but won't grow. Only other thing I can think of is manually setting the MaxWidth.TyCobb

2 Answers

1
votes

Here's my final solution, that also works for multiline TextBoxes. It will even work when pasting-in text. The only weirdness is that I delete trailing characters when text overflows, and that may seem strange if you are typing in text in the middle of the text box. I tried working around that by removing characters at the point of CaretIndex, but it was getting too involved. But other than that it does what I need it to do. To improve performance you can cache results from GetLineHeight function, so that you only need to call it once per TextBox (EDIT - I added the code for that as well).

<TextBox Height="23" Width="120" TextWrapping="Wrap"  TextChanged="TextBoxTextChanged" AcceptsReturn="True"/>

private void TextBoxTextChanged(object sender, TextChangedEventArgs e)
{
    TextBox textBox = sender as TextBox;
    if (textBox == null)
        return;

    double textLineHeight = GetCachedTextLineHeight(textBox);
    int maxTextBoxLines = (int)(textBox.ViewportHeight / textLineHeight);

    while (textBox.LineCount > maxTextBoxLines) //if typed in text goes out of bounds
    {
        if (textBox.Text.Length > 0)
            textBox.Text = textBox.Text.Remove(textBox.Text.Length - 1, 1); //remove last character

        if (textBox.Text.Length > 0)
            textBox.CaretIndex = textBox.Text.Length;
    }
}

private double GetTextLineHeight(TextBox textBox)
{
    FormattedText formattedText = new FormattedText(
        "a",
        CultureInfo.CurrentUICulture,
        FlowDirection.LeftToRight,
        new Typeface(textBox.FontFamily, textBox.FontStyle, textBox.FontWeight, textBox.FontStretch),
        textBox.FontSize,
        Brushes.Black);

    return formattedText.Height;
}

#region Caching

Dictionary<TextBox, double> _cachedLineHeights = new Dictionary<TextBox, double>();

private double GetCachedTextLineHeight(TextBox textBox)
{
    if (!_cachedLineHeights.ContainsKey(textBox))
    {
        double lineHeight = GetTextLineHeight(textBox);
        _cachedLineHeights.Add(textBox, lineHeight);
    }

    return _cachedLineHeights[textBox];
}

#endregion
0
votes

You're looking for the MaxLength property. You'll have to experiment with the number since you need to remember that you can fit a lot of i's in the space you can fit one W in. Typically when I size TextBoxes, I size and set the max length based on W's since thats the widest character.

EDIT: just saw you want it to work with multi-line text boxes too... in that case, just set it to wrap and it won't scroll horizontally.