Search code examples
wpfbindingtriggersstylesrelativesource

WPF Bind to a property on the Style's target in Trigger


I want to have a GeometryDrawing inside a Button that changes it's Geometry when the mouse is over the Button:

<Style TargetType="{x:Type Button}">
    <Setter Property="Content">
        <Setter.Value>
            <Rectangle>
                <Rectangle.Fill>
                    <DrawingBrush>
                        <DrawingBrush.Drawing>
                            <DrawingGroup>
                                <DrawingGroup.Children>
                                    <!-- This Binding works -->
                                    <GeometryDrawing Brush="{Binding Foreground, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Button}}}"
                                                     Geometry="M8,8 1,8 1,3 8,3z M9,9 0,9 0,0 9,0z" />
                                </DrawingGroup.Children>
                            </DrawingGroup>
                        </DrawingBrush.Drawing>
                    </DrawingBrush>
                </Rectangle.Fill>
            </Rectangle>
        </Setter.Value>
    </Setter>
    <Style.Triggers>
        <Trigger Property="IsMouseOver" Value="True">
            <Setter Property="Content">
                <Setter.Value>
                    <Rectangle>
                        <Rectangle.Fill>
                            <DrawingBrush>
                                <DrawingBrush.Drawing>
                                    <DrawingGroup>
                                        <DrawingGroup.Children>
                                            <!-- This Binding does not work -->
                                            <GeometryDrawing Brush="{Binding Foreground, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Button}}}"
                                                             Geometry="M6,9 1,9 1,5 6,5z M7,10 0,10 0,3 7,3z  M10,7 7,7 7,6 9,6 9,2 4,2 4,3 3,3 3,0 10,0z" />
                                        </DrawingGroup.Children>
                                    </DrawingGroup>
                                </DrawingBrush.Drawing>
                            </DrawingBrush>
                        </Rectangle.Fill>
                    </Rectangle>
                </Setter.Value>
            </Setter>
        </Trigger>
    </Style.Triggers>
</Style>

However, it gives me this error

System.Windows.Data Error: 4 : Cannot find source for binding with reference 'RelativeSource FindAncestor, AncestorType='System.Windows.Controls.Button', AncestorLevel='1''.

and I do not know why, since the Binding does work in the Style's Setter, but not in the Style's Trigger's Setter.

I have no idea why it does not work in the latter or how to solve it.

Any help is greatly appreciated!

EDIT: I also tried {TemplateBinding Button.Foreground} and {RelativeSource = {RelativeSource TemplatedParent}}, which both did not work, so the question now is: how can I bind to a property on the Style's target in a Trigger?


Solution

  • Changing the Content from Style Setters and Triggers is bad practice, it allows all kinds of issues with the style caching elements and using the same element as the content for multiple buttons in your case, which is not allowed in wpf.

    The Binding from the Triggers cannot work since it's not in the same visual tree as the button at the moment of creation. This is what the data error is telling you.

    What you really want is to change the ControlTemplate of the button. The difference is that new elements from ControlTemplate is created for each button that has this style.

    Control template sample:

    <ControlTemplate x:Key="ButtonBaseControlTemplate1" TargetType="{x:Type ButtonBase}">
    <Border x:Name="border" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" SnapsToDevicePixels="True">
        <Grid>
            <Rectangle x:Name="normalIcon">                           
                //same as before
            </Rectangle>
            <Rectangle x:Name="mouseOverIcon" Visibility="Collapsed">
                //same as before
            </Rectangle>
            <ContentPresenter x:Name="contentPresenter" ContentTemplate="{TemplateBinding ContentTemplate}" Content="{TemplateBinding Content}" ContentStringFormat="{TemplateBinding ContentStringFormat}" Focusable="False" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" RecognizesAccessKey="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
        </Grid>
    </Border>
    <ControlTemplate.Triggers>
       //other triggers from default template
        <Trigger Property="IsMouseOver" Value="True">
            <Setter Property="Background" TargetName="border" Value="#FFBEE6FD"/>
            <Setter Property="BorderBrush" TargetName="border" Value="#FF3C7FB1"/>
            <Setter Property="Visibility" TargetName="normalIcon" Value="Collapsed"/>
            <Setter Property="Visibility" TargetName="mouseOverIcon" Value="Visible"/>
        </Trigger>
        //other triggers from default template
    </ControlTemplate.Triggers>