5

I would like to set the Width of a WPF TextBox so that it will have enough space for, say, any TCP port number with 5 digits. It should not be larger, and it should not resize dynamically, i.e. Width="Auto" is not what I want.

I'm looking for a generic way, i.e. one that respects the font used, and I don't want to have to fiddle around with a pixel-exact Width value when the font - or anything else that might change the pixel width of 5 digits - is changed.

I guess it would be possible - if awkward - to do in code via MeasureString, but is this possible in XAML?

Evgeniy Berezovsky
  • 18,571
  • 13
  • 82
  • 156
  • 1
    I can think of ways to accomplish this from code, but I doubt there is going to be a xaml only solution. Also, this question has been asked here before (without the xaml restriction), in case any of these answers are useful to you: http://stackoverflow.com/questions/9264398, http://stackoverflow.com/questions/12138099, http://stackoverflow.com/questions/2363725 – Xavier Jun 05 '15 at 02:43
  • You probably can use ViewBox as the most generic way to accomplish this task (see:http://stackoverflow.com/questions/2282662/wpf-textblock-font-resize-to-fill-available-space-in-a-grid). Best regards, – Alexander Bell Jun 05 '15 at 02:55
  • @AlexBell The question you refer to is trying something different, and the ViewBox [docs](https://msdn.microsoft.com/en-us/library/system.windows.controls.viewbox%28v=vs.110%29.aspx) suggest this is not what I'm asking for: The ViewBox class `Defines a content decorator that can stretch and scale a single child to fill the available space.` – Evgeniy Berezovsky Jun 05 '15 at 03:15
  • In practice, ViewBox will set the actual font-size of the encapsulated TextBox to fit the width/height you have allocated for it, either by setting fixed width or using relative units. I have used this XAML solution in many commercial-grade apps (demo is available) and it's proven to be very reliable (it also works fine for TextBlock controls). Alternatively, this font-size fit-adjusting functionality could be implemented in C# (code behind), but I do prefer a XAML approach. Best regards, – Alexander Bell Jun 05 '15 at 05:08

1 Answers1

2

Well, it may not be perfect, but here is a possible solution.

Create a ControlTemplate which will contain a desired CharacterLength and a GhostString dependency property.

public class DynamicTextBox : TextBox
{
    public int CharacterLength
    {
        get { return (int)GetValue(CharacterLengthProperty); }
        set { SetValue(CharacterLengthProperty, value); }
    }

    // Using a DependencyProperty as the backing store for CharacterLength.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty CharacterLengthProperty =
        DependencyProperty.Register("CharacterLength", typeof(int), typeof(DynamicTextBox), new PropertyMetadata(5, new PropertyChangedCallback(CharacterLengthChanged)));

    public string GhostString
    {
        get { return (string)GetValue(GhostStringProperty); }
        private set { SetValue(GhostStringProperty, value); }
    }

    // Using a DependencyProperty as the backing store for GhostString.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty GhostStringProperty =
        DependencyProperty.Register("GhostString", typeof(string), typeof(DynamicTextBox), new PropertyMetadata("#####"));

    static DynamicTextBox()
    {
        DefaultStyleKeyProperty.OverrideMetadata(typeof(DynamicTextBox), new FrameworkPropertyMetadata(typeof(DynamicTextBox)));
    }

    private static void CharacterLengthChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        DynamicTextBox textbox = d as DynamicTextBox;

        string ghost = string.Empty;

        for (int i = 0; i < textbox.CharacterLength; i++)
            ghost += "#";

        textbox.GhostString = ghost;
    }
}

Whenever the CharacterLength property changes, then the GhostString property will be recalculated, you'll see the magic in a minute.

Set the Style and ControlTemplate for this new control.

<Style TargetType="{x:Type local:DynamicTextBox}"
       BasedOn="{StaticResource {x:Type TextBox}}">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type local:DynamicTextBox}">
                <Border Background="{TemplateBinding Background}"
                        BorderBrush="{TemplateBinding BorderBrush}"
                        BorderThickness="{TemplateBinding BorderThickness}">
                    <Grid>
                        <TextBlock Text="{TemplateBinding GhostString}"
                                   Visibility="Hidden"
                                   Margin="3,0"/>

                        <ScrollViewer Margin="0"
                                  x:Name="PART_ContentHost" />
                    </Grid>
                </Border>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

The GhostString property is placed inside a Hidden TextBlock, this means that the width is rendered, but the text is invisible, it's placed behind the TextBox anyway.

You can use the control like so:

<Controls:DynamicTextBox CharacterLength="12" HorizontalAlignment="Left"/>
    <Controls:DynamicTextBox CharacterLength="6" HorizontalAlignment="Left"/>
    <Controls:DynamicTextBox CharacterLength="2" HorizontalAlignment="Left"/>

Note: I put the HorizontalAlignment there just to force the width to collapse.

The result looks like this:

TextBoxes inside a StackPanel

It's not perfect, however it's certainly a start. If you wanted to further restrict the width of the TextBox, I'm fairly certain you can do some clever binding inside the ControlTemplate.

Mike Eason
  • 9,525
  • 2
  • 38
  • 63
  • 1
    In other words: Roll your own. Ideally, I'd like to avoid that, but thanks for giving me the option. – Evgeniy Berezovsky Jun 09 '15 at 04:33
  • Pretty much. As far as I am aware, there is no way this can be achieved solely in XAML. You might be able to adjust widths in C#, but if you're going to do that, you might as well use a custom control, as I have done. I'm quite pleased with the answer I gave you, it even solves the problem of different font sizes. Let me know how you get on. – Mike Eason Jun 09 '15 at 08:24