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?
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:
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
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With