wpfxamlstylesapp.xaml

How to Target WPF child controls with nested styles by name?


I am experimenting with creating a custom button in WPF.

I have basic XAML for a Button, with two TextBlock controls inside the button. One will be an image rendered by FontAwesome, and one will be text.

<TextBlock Style="{DynamicResource mediumLabel}" Grid.Column="1" Grid.ColumnSpan="2" Grid.Row="4">SETTINGS</TextBlock>
<Button  Grid.Column="1" Grid.Row="5" Grid.ColumnSpan="2" HorizontalAlignment="Stretch" Style="{DynamicResource mainButton}" Template="{DynamicResource mainButtonTemplate}">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="40"/>
            <ColumnDefinition Width="200"/>
        </Grid.ColumnDefinitions>

        <TextBlock x:Name="Image" Grid.Row="1" Grid.Column="0">cogs</TextBlock>
        <TextBlock x:Name="Label" Grid.Row="1" Grid.Column="1" Text="SETTINGS" />
    </Grid>
</Button>

I have global styles defined in App.xaml.

I can target each of these three elements individually, with individual styles in my App.xaml.

What I would like to do, I guess just for organization and ease of future use, I want to have a style for the Button, with nested styles to target each of the two TextBlock controls. Each will be styled differently, so I cant target the TextBlock type. I want to reference them by name.

I have tried referencing the main control_name.childcontrol_name, as well as just the control name.

I can't seem to get enough info on how to do this when searching, as I might be searching for the wrong terminology...

My Attempt at the nested style, with two attempts for the nested styles targeting.

<Style x:Key="mainButton" TargetType="Button">
    <Setter Property="Background" Value="White"/>
    <Setter Property="Foreground" Value="DarkSlateGray"/>
    <Setter Property="FontSize"  Value="14"/>
    <Setter Property="BorderThickness" Value="0"/>
    <Setter Property="Cursor" Value="Hand"/>
    <Style.Triggers>
        <Trigger Property="IsMouseOver" Value="True">
            <Setter Property="Foreground" Value="DodgerBlue"/>
        </Trigger>
    </Style.Triggers>

    <Style.Resources>
        <Style TargetType="{x:Reference Image}">
            <Setter Property="FontFamily" Value="/Genesis_desktop;component/tools/fontawesome-free-5.15.1-desktop/otfs/#Font Awesome 5 Free Solid"/>
            <Setter Property="FontSize"  Value="14"/>
            <Setter Property="VerticalAlignment" Value="Center"/>
            <Setter Property="HorizontalAlignment" Value="Center"/>
            <Setter Property="ForeGround" Value="orange"/>
        </Style>

        <Style TargetType="{x:Reference mainButton.Label}">
            <Setter Property="FontSize"  Value="14"/>
            <Setter Property="VerticalAlignment" Value="Center"/>
            <Setter Property="HorizontalAlignment" Value="Left"/>
        </Style>
    </Style.Resources>
</Style>

Solution

  • You are thinking the wrong way. It's not the Style that searches its target. It's the target that searches its Style.
    The XAML parser creates an instance of the markup object e.g. a <TextBlock> and then, after checking the local FrameworkElement.Style property, traverses the logical tree to find if there is somewhere a Style defined that targets this instantiated type or that has a key, that matches the requested resource key.

    What you want would be also quite inefficient as the name inside a namescope must be unique and namescopes are always quite "small". Such a element name driven mapping would require an explicit style that only matches a single control. And it would require to know the name of this control in advance.
    This means you, the author of this individual Style, have direct access to the named control.
    You could therefore define the style directly in the local ResourceDictionary e.g. TextBlock.Resources.
    This has the same effect: the Style now applies to a single TextBlock element, that you know by name.

    If you need to declare the exclusive style on a different parent ResourceDictionary or as merge resource, you usually name the Style by applying the x:Key directive and then request this resource explicitly e.g. by using StaticResource markup extension.

    Also x:Reference returns a name and not a Type. The TargetType property is of type Type. This property uses a TypeConverter, which allows to assign a string value which is then converted to Type.

    What you want is not possible by default. Styles are pure type specific (when defined as implicit resource) or are mapped explicitly by their x:Key value (as additional constraint). On the other hand, what you want is already there in a different form and can be achieved by using local resources or the x:Keydirective together with the StaticResource or DynamicResource markup extension.