dlf
dlf

Reputation: 9403

TemplatedParent is null when used inside a ControlTemplate's DataTrigger

Consider this (edited-down) Style, designed for a Button whose Content is a String:

<Style x:Key="Test" TargetType="Button">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="Button">
               <StackPanel>
                   <TextBlock x:Name="text" Text="{TemplateBinding Content}" />
                   <TextBlock x:Name="demo" Text="{Binding RelativeSource={RelativeSource TemplatedParent}}" />
                </StackPanel>
                <ControlTemplate.Triggers>
                    <DataTrigger Binding="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Content}">
                        <DataTrigger.Value>
                            <system:String>Test</system:String>
                        </DataTrigger.Value>
                        <Setter TargetName="test" Property="Foreground" Value="Red" />
                    </DataTrigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

The intention in this example is to turn the button text red if it equals the word "Test"1. But it doesn't work, because the trigger's TemplatedParent binding resolves to null instead of to the Button the Style is applied to. However, the TextBlock named "demo" will have its Text set to "System.Windows.Controls.Button: [ButtonText]" as expected, which means TemplatedParent works correctly at that level. Why doesn't it work inside the DataTrigger?


1 I know there are other ways to achieve that, but I'm trying to understand why the binding doesn't work the way I expect it to.

Upvotes: 7

Views: 2220

Answers (3)

mihails.kuzmins
mihails.kuzmins

Reputation: 1440

I think it might be a similar issue in .NET Core WPF. My DataTrigger was not firing with {Binding MyProp, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource Convert}}, but instead when I changed the RelativeSource to Self the binding started to work. I'm not sure whether it's a hack or a solution, but it worked.

Maybe it's worth mentioning that my template was based on MyView (see below) and I was binding to a DependencyProperty on MyView.

So my final code looked like this:

<ControlTemplate x:Key="Template" TargetType="{x:Type ns:MyView}">
    <!-- Some template -->

    <ControlTemplate.Triggers>
        <DataTrigger Binding="{Binding MyProp, RelativeSource={RelativeSource Self}, Converter={StaticResource Convert}}" Value="True">
            <Setter Property="Foreground" Value="Red"/>
        </DataTrigger>
    </ControlTemplate.Triggers>
</ControlTemplate>

Upvotes: 1

Evk
Evk

Reputation: 101613

TemplatedParent in your ControlTemplate.Triggers is not what you expect. Inside trigger it actually references Button.TemplatedParent. As such, it will only be non-null if your create that button inside template. You don't create button inside template, so it is null in your case. Now consider this xaml:

<Window.Resources>
    <Style x:Key="Test"
           TargetType="Button">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="Button">
                    <StackPanel>
                        <TextBlock x:Name="text"
                                   Text="dummy" />
                        <TextBlock x:Name="demo"
                                   Text="{Binding RelativeSource={RelativeSource TemplatedParent}}" />
                    </StackPanel>
                    <ControlTemplate.Triggers>
                        <DataTrigger Binding="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Content}">
                            <DataTrigger.Value>
                                <system:String>Test</system:String>
                            </DataTrigger.Value>
                            <Setter TargetName="text"
                                    Property="Foreground"
                                    Value="Red" />
                        </DataTrigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
    <Style x:Key="Test2" TargetType="ContentControl">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="ContentControl">
                    <Button Style="{StaticResource Test}"></Button>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</Window.Resources>
<Grid>
    <!--<Button Content="Test" Style="{StaticResource Test}"/>-->
    <ContentControl Style="{StaticResource Test2}" Content="Test" />
</Grid>

Here I retemplate ContentControl and inside template I use button with your template. If you run this code, you will see "dummy" text in red, because Button.TemplatedParent is now ContentControl, and it has it's Content equals "Test", which confirms what I said above.

Now back to your problem: just change RelativeSource TemplatedParent to RelativeSource Self (no need to change DataTrigger to Trigger) - this one would reference your Button.

Upvotes: 13

Bass Guru
Bass Guru

Reputation: 26

I'm not quite sure, but I think the trigger equals by referenc, because Content returns an Object. So it will never be true with your string defined within the trigger.

Upvotes: -1

Related Questions