How to apply TextBox Control Template?

20,006

Solution 1

Thanks to jure for getting me started. As always, it turns out that the solution is a bit more convoluted than it originally lets on.

Let's examine what I did.

Using the ideas presented in the previous post, and after a bit of finagling, I came up with the following code.

<Style TargetType="{x:Type TextBox}"
       x:Key="ExpandingTextBox">
    <Setter Property="OverridesDefaultStyle"
            Value="True" />
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="TextBox">
                <Grid>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="1*">
                            <ColumnDefinition.MaxWidth>
                                <Binding Path="MaxWidth"
                                         RelativeSource="{RelativeSource TemplatedParent}">
                                </Binding>
                            </ColumnDefinition.MaxWidth>
                        </ColumnDefinition>
                        <ColumnDefinition Width="0*" />
                    </Grid.ColumnDefinitions>
                    <TextBox  Text="{TemplateBinding Text}">
                        <TextBox.MaxWidth>
                            <Binding Path="MaxWidth"
                                     RelativeSource="{RelativeSource TemplatedParent}" />
                        </TextBox.MaxWidth>
                    </TextBox>
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

(Code Listing 6)

And of course applied it in the XAML markup for my page like this...

<TextBox  Grid.Column="1"
          Grid.Row="3"
          Text="{Binding Path=Username}"
          Style="{StaticResource ExpandingTextBox}"
          Margin="10"
          MaxWidth="200" />

(Code Listing 7)

That gives a result like this...

Incorrect Style Template

(Figure 4)

After looking at that image, and thinking for a while, it donned on me that the problem was that even though I was applying the max width inside the style correctly, the max width property was still being applied to the root of the control in XAML.

Essentially it was squeezing it down to the max width on the page, and then after that was done, it applied the max width pursuant to the styling!

So simple, but so elusive!

Therefore, I modified the style to use the Tag property to apply to the styled max width elements, since the Tag property doesn't do anything at the root.

<Style TargetType="{x:Type TextBox}"
       x:Key="ExpandingTextBoxMaxWidthInTag">
    <Setter Property="OverridesDefaultStyle"
            Value="True" />
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="TextBox">
                <Grid>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="1*">
                            <ColumnDefinition.MaxWidth>
                                <Binding Path="Tag"
                                         RelativeSource="{RelativeSource TemplatedParent}">
                                </Binding>
                            </ColumnDefinition.MaxWidth>
                        </ColumnDefinition>
                        <ColumnDefinition Width="0*" />
                    </Grid.ColumnDefinitions>
                    <TextBox  Text="{TemplateBinding Text}">
                        <TextBox.MaxWidth>
                            <Binding Path="Tag"
                                     RelativeSource="{RelativeSource TemplatedParent}" />
                        </TextBox.MaxWidth>
                    </TextBox>
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

(Code Listing 8)

Which is applied in XAML like this...

<TextBox  Grid.Column="1"
          Grid.Row="3"
          Text="{Binding Path=Username}"
          Style="{StaticResource ExpandingTextBoxMaxWidthInTag}"
          Margin="10"
          Tag="200" />

And finally gives the desired results like this...

Desired Result with Layout Shown

(Figure 5)

Desired Result without Layout Shown

(Figure 6)

So, the real trick was realizing that I didn't necessarily want to limit the max width of the entire templated control, but instead just wanted to limit the width of the TextBox internal to the control!

Once I figured that out, it only made sense to use the Tag property which wasn't doing anything to the control at the root level!

I hope this helps anyone else who might have this problem!

Solution 2

You need a TextBox instead of ContentPresenter inside your Template. You replaced default template for TextBox with one that no longer has area for text input. Try like this:

 <Style TargetType="{x:Type TextBox}" x:Key="ExpandingTextBox">
        <Setter Property="OverridesDefaultStyle" Value="True"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="TextBox">
                    <Grid>
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="1*"
                                      MaxWidth="170" />
                            <ColumnDefinition Width="0*" />
                        </Grid.ColumnDefinitions>
                        <TextBox  Text="{TemplateBinding Text}"
                                  VerticalAlignment="Center"
                                  HorizontalAlignment="Stretch"
                                  Margin="10"
                                  MaxWidth="100"
                         />
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

This should work but imo, is a bit wierd..

Share:
20,006
imdandman
Author by

imdandman

Updated on April 27, 2020

Comments

  • imdandman
    imdandman about 4 years

    I'm trying to simplify some code and improve my maintainability.

    I was initially seeking a way to have a text box aligned to the left, that can shrink and expand to a maximum value without centering inside a grid cell after reaching the maximum value.

    So I started out writing some code like this...

    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="200" />
        </Grid.ColumnDefinitions>
        <TextBox Text="Some text"
                 VerticalAlignment="Center"
                 HorizontalAlignment="Stretch"
                 Margin="10"
                 MaxWidth="150" />
    </Grid>
    

    (Code Listing 1)

    And that yields something looking like this...

    TextBox in Grid Centered

    (Figure 1)

    As you can see, this centers the TextBox with it's max width, but it is aligned in the center of the grid cell.

    So then I though maybe if i changed the horizontal alignment of the text box like so...

    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="200" />
        </Grid.ColumnDefinitions>
        <TextBox  Text="Some text"
                  VerticalAlignment="Center"
                  HorizontalAlignment="Left"
                  Margin="10"
                  MaxWidth="100" />
    </Grid>
    

    (Code Listing 2)

    But unfortunately, that only yields something looking like this...

    Too small TextBox left aligned

    (Figure 2)

    Again, this isn't right because, despite all the empty space on the right, the text box is not expanding to its max width of 100 like I want.

    Eventually, I found that if put the TextBox inside a nested grid, you will achieve the desired effect.

    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="200" />
        </Grid.ColumnDefinitions>
        <Grid>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="1*"
                                  MaxWidth="120" />
                <ColumnDefinition Width="0*" />
            </Grid.ColumnDefinitions>
            <TextBox Text="Some text"
                 VerticalAlignment="Center"
                 HorizontalAlignment="Stretch"
                 Margin="10"
                 MaxWidth="100" />
        </Grid>
    </Grid>
    

    (Code Listing 3)

    Which yields this...

    Correct looking TextBox

    (Figure 3)

    As you can see, the nested grid fills the entire 200 wide space, but the text box fills the left 100, and leaves the empty space on the right. (If this window is re-sizable it will shrink and expand appropriately)

    I would like to apply the methods of Code Listing 3 to a style or control template, so I can blanket apply it to TextBoxes where this situation is applicable.

    In a perfect scenario, I would do something like...

    <TextBox Text="Some text"
             VerticalAlignment="Center"
             HorizontalAlignment="Stretch"
             Margin="10"
             MaxWidth="100"
             Style="{StaticResource ExpandingTextBox}" /> 
    

    (Code Listing 4)

    And achieve the same results as Figure 3.

    I tried the following...

    <Style TargetType="{x:Type TextBox}"
           x:Key="ExpandingTextBox">
        <Setter Property="OverridesDefaultStyle"
                Value="True"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="TextBox">
                    <Grid>
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="1*"
                                              MaxWidth="170" />
                            <ColumnDefinition Width="0*" />
                        </Grid.ColumnDefinitions>
                        <ContentPresenter/>
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
    

    (Code Listing 5)

    But that just causes the text box to disappear from my form all together. I'm not very good at writing templates like this, so I'm kind of at a loss.

    Also, one thing I just realized is that the width of the nested column = MaxWidth + 2 x Margin. I wouldn't mind setting the column width explicitly in the TextBox declaration, but if it could be automatic, that would be awesome.