Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Set Width of TextBox in XAML so it'll be able to display x digits

Tags:

wpf

xaml

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?

like image 276
Evgeniy Berezovsky Avatar asked Oct 31 '22 02:10

Evgeniy Berezovsky


1 Answers

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.

like image 88
Mike Eason Avatar answered Nov 24 '22 00:11

Mike Eason