2
votes

So, I've got a custom WPF control called WatermarkTextbox which extends TextBox. The only thing I added to the code is a string dependency property to hold the watermark text. The rest of the magic is in the Xaml (below).

<Style TargetType="wpfControls:WatermarkTextbox" >
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type wpfControls:WatermarkTextbox}">
                <Grid>
                    <TextBox x:Name="baseTextBox" />
                    <TextBlock Margin="5,0,0,0" x:Name="watermarkText" IsHitTestVisible="False" FontWeight="Light" FontStyle="Italic" Foreground="DarkGray" Visibility="Hidden" Background="Transparent"
                               Text="{Binding RelativeSource={RelativeSource AncestorType=wpfControls:WatermarkTextbox}, Path=Watermark}" />
                </Grid>
                <ControlTemplate.Triggers>
                    <Trigger SourceName="baseTextBox" Property="Text" Value="">
                        <Setter TargetName="watermarkText" Property="Visibility" Value="Visible"/>
                    </Trigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

and when used in my application:

<wpfControls:WatermarkTextbox Watermark="Text that disappears."/>

This works, mostly. I can set the watermark text, and when I start entering some text, it goes away. When I change the font size, it changes both the watermark and the text I enter; when I change the font weight, it only changes the text entered (which is what I want it to do). I can change the size of the textbox. That's all gravy.

The problem is when I start trying to change things like the textbox's background or border properties, like so.

<wpfControls:WatermarkTextbox Watermark="Text that disappears." Background="Yellow"/>

Nothing happens. Same behavior with BorderBrush and BorderThickness. Now, the part where I know just enough to know that there's some important concept that I don't know. If I change the template for my WatermarkTextbox to the following, it will let me set the background in my application like I want.

<Style TargetType="wpfControls:WatermarkTextbox" >
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type wpfControls:WatermarkTextbox}">
                <Grid>
                    <TextBox x:Name="baseTextBox" 
                             Background="{TemplateBinding Background}"/>
                    <TextBlock Margin="5,0,0,0" x:Name="watermarkText" IsHitTestVisible="False" FontWeight="Light" FontStyle="Italic" Foreground="DarkGray"
                               Text="{Binding RelativeSource={RelativeSource AncestorType=wpfControls:WatermarkTextbox}, Path=Watermark}" Visibility="Hidden" Background="Transparent"/>
                </Grid>
                <ControlTemplate.Triggers>
                    <Trigger SourceName="baseTextBox" Property="Text" Value="">
                        <Setter TargetName="watermarkText" Property="Visibility" Value="Visible"/>
                    </Trigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

I assume that if I did the same for BorderBrush and BorderThickness, they would work as well.

So, my question is, why? What is it about these properties that makes them behave differently from FontSize and FontWeight or Height and Width? Why do I have to explicitly set the Background to {TemplateBinding Background} but not FontSize? Also, what other properties do I need to set to the TemplateBinding in order to make them work properly?

2

2 Answers

3
votes

Some of the properties are inherited from their parent automatically. explanation

This is why you don't have to set FontSize.

As for "what else", it all depends on what you want to be able to set directly on the user control.

Although this isn't bullet proof, but my general rule of thumb is "if it is a property in the Brush Tab of the property window or is purely for visual aesthetics, it probably is not inherited"

Another way to look at it - if the setting would give weird results in general, it also is probably no inherited. Example: if you set the Margin property on a Grid, imagine if every sub-element inherited the same margins.

So i typically add the template bindings for all the non-Layout, visual properties (Background, Foreground, BorderBrush, etc.). Or i just add templatebindings for any properties i want to set directly to my usercontrol. There is no need to add a template binding if you never intend to set the property (explicitly or by style).

3
votes

Anything else, you need to explicitly do the TemplateBinding thing -- or if they'll change at runtime, you'll need to do a relativesource binding:

{Binding PropertyName, RelativeSource={RelativeSource TemplatedParent}}

There isn't any "first principles" explanation for this; it's all just arbitrary implementation details.