Search code examples
wpfxamlwpf-style

Why Are Some WPF Custom Style Elements Ignored Whilst Others Adhered?


I'm a bit confused trying to understand how style overrides inside the visual tree work.

I've got two examples - the first, defining DataGridCell works perfectly, as shown.

The second, defining ToggleButton, is completely ignored, but I'm hard pushed to find out why the first one works, but the second does not. Can anyone offer any insight?

Working - DataGridCell Style Defined in DataGrid.Resources:

<DataGrid AutoGenerateColumns="False" ItemsSource="{Binding hello}">
    <DataGrid.Resources>
        <Style TargetType="{x:Type DataGridCell}"><!--DataGridCell is a child of DataGrid's Visual Tree -->
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate>
                        <Border Padding="10" Background="Red">
                          <ContentPresenter Content="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Content}" />
                        </Border>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </DataGrid.Resources>

working


Non-Working - ToggleButton Style defined in Resources is ignored:

<Expander>
    <Expander.Resources>
        <Style TargetType="{x:Type ToggleButton}"><!--ToggleButton is a child of Expander's Visual Tree-->
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate>
                        <TextBlock>Hello World!</TextBlock>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </Expander.Resources>
    <Expander.Header>Header</Expander.Header>
    <Expander.Content>Body</Expander.Content>
</Expander>

enter image description here


Solution

  • Some controls define their own Styles for the controls they use. For example the default template for Expander defines a ControlTemplate for ToggleButton and sets it like this (I dont want to copy the whole default style in here because its quite long):

    <ToggleButton x:Name="HeaderSite" ContentTemplate="{TemplateBinding HeaderTemplate}" 
        ContentTemplateSelector="{TemplateBinding HeaderTemplateSelector}" 
        Content="{TemplateBinding Header}" ... Style="{StaticResource ExpanderDownHeaderStyle}" ... />
    
    <Style x:Key="ExpanderDownHeaderStyle" TargetType="{x:Type ToggleButton}">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type ToggleButton}">
                    <Border Padding="{TemplateBinding Padding}">
                        <Grid Background="Transparent" SnapsToDevicePixels="False">
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="19"/>
                                <ColumnDefinition Width="*"/>
                            </Grid.ColumnDefinitions>
                            <Ellipse x:Name="circle" Fill="{StaticResource Expander.Static.Circle.Fill}" HorizontalAlignment="Center" Height="19" Stroke="{StaticResource Expander.Static.Circle.Stroke}" VerticalAlignment="Center" Width="19"/>
                            <Path x:Name="arrow" Data="M 1,1.5 L 4.5,5 L 8,1.5" HorizontalAlignment="Center" SnapsToDevicePixels="false" Stroke="{StaticResource Expander.Static.Arrow.Stroke}" StrokeThickness="2" VerticalAlignment="Center"/>
                            <ContentPresenter Grid.Column="1" HorizontalAlignment="Left" Margin="4,0,0,0" RecognizesAccessKey="True" SnapsToDevicePixels="True" VerticalAlignment="Center"/>
                        </Grid>
                    </Border>
                    <ControlTemplate.Triggers>
                        <Trigger Property="IsChecked" Value="true">
                            <Setter Property="Data" TargetName="arrow" Value="M 1,4.5  L 4.5,1  L 8,4.5"/>
                        </Trigger>
                        <Trigger Property="IsMouseOver" Value="true">
                            <Setter Property="Stroke" TargetName="circle" Value="{StaticResource Expander.MouseOver.Circle.Stroke}"/>
                            <Setter Property="Fill" TargetName="circle" Value="{StaticResource Expander.MouseOver.Circle.Fill}"/>
                            <Setter Property="Stroke" TargetName="arrow" Value="{StaticResource Expander.MouseOver.Arrow.Stroke}"/>
                        </Trigger>
                        <Trigger Property="IsPressed" Value="true">
                            <Setter Property="Stroke" TargetName="circle" Value="{StaticResource Expander.Pressed.Circle.Stroke}"/>
                            <Setter Property="StrokeThickness" TargetName="circle" Value="1.5"/>
                            <Setter Property="Fill" TargetName="circle" Value="{StaticResource Expander.Pressed.Circle.Fill}"/>
                            <Setter Property="Stroke" TargetName="arrow" Value="{StaticResource Expander.Pressed.Arrow.Stroke}"/>
                        </Trigger>
                        <Trigger Property="IsEnabled" Value="false">
                            <Setter Property="Stroke" TargetName="circle" Value="{StaticResource Expander.Disabled.Circle.Stroke}"/>
                            <Setter Property="Fill" TargetName="circle" Value="{StaticResource Expander.Disabled.Circle.Fill}"/>
                            <Setter Property="Stroke" TargetName="arrow" Value="{StaticResource Expander.Disabled.Arrow.Stroke}"/>
                        </Trigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
    

    Because this is a local value, it takes precedence over your style that is defined as a resource.
    As dkozl mentioned in the comments, check Dependency Property Setting Precedence List to find out which takes precedence over what.