When designing custom window controls or dark-themed buttons in WPF, a common frustrating issue occurs: you create a custom ControlTemplate, customize the hover and press states, but the button text color (Foreground) refuses to change—even though setting the background works fine.

The Core Problem

In WPF, the Foreground property on controls like Button is not a native property of ContentPresenter. When you provide plain text as the Button.Content, WPF generates a lightweight TextBlock inside the ContentPresenter. That TextBlock inherits the attached property TextElement.Foreground down the visual tree.

There are two primary reasons your text foreground isn't changing as expected:

  • Triggers inside ControlTemplate: When using <Setter Property="Foreground" Value="..." /> inside <ControlTemplate.Triggers> without a TargetName, it modifies the template parent, but the visual text elements inside the template often fail to receive property change notifications unless targeted directly using TextElement.Foreground.
  • Manual Content Binding on ContentPresenter: Explicitly writing Content="{TemplateBinding Content}" on a ContentPresenter inside a ControlTemplate disables the automatic property aliasing that WPF performs for ContentControl elements.

Why Did the Canvas Work?

In your custom UserControl with a Canvas, you used an explicit binding:

Fill="{Binding RelativeSource={RelativeSource AncestorType=Button}, Path=Foreground}"

This explicitly searched up the visual tree for the Button instance and fetched its Foreground value directly. However, standard text relies entirely on ambient property inheritance via TextElement.Foreground, which was being intercepted or not propagated by the template.

The Solution

To fix this cleanly and ensure both direct properties (like Foreground="Maroon") and trigger states (like IsMouseOver) work for text and icons, follow these two adjustments:

1. Update ControlTemplate.Triggers to Target TextElement.Foreground

Specify TargetName="contentPresenter" (or target the parent Border) and use TextElement.Foreground instead of raw Foreground in your template triggers.

2. Remove Explicit TemplateBindings from ContentPresenter

Allow the ContentPresenter to automatically inherit its content and properties from the templated Button.

Complete Working Style Example

Here is the corrected and modernized WindowControl button style:

<Style x:Key="WindowControl" TargetType="{x:Type Button}">   <Setter Property="Padding" Value="0,0,0,4"/>   <Setter Property="VerticalContentAlignment" Value="Center"/>   <Setter Property="HorizontalContentAlignment" Value="Center"/>   <Setter Property="Width" Value="45"/>   <Setter Property="Height" Value="35"/>   <Setter Property="FontSize" Value="13"/>   <Setter Property="FontWeight" Value="Normal"/>   <Setter Property="Background" Value="{StaticResource Button.Static.Control.Background}"/>   <Setter Property="Foreground" Value="{StaticResource Button.Static.Control.Foreground}"/>   <Setter Property="BorderThickness" Value="0"/>      <Setter Property="Template">       <Setter.Value>           <ControlTemplate TargetType="{x:Type Button}">               <Border x:Name="border"                        Background="{TemplateBinding Background}"                        BorderBrush="{TemplateBinding BorderBrush}"                        BorderThickness="{TemplateBinding BorderThickness}"                        SnapsToDevicePixels="True">                   <ContentPresenter x:Name="contentPresenter"                                     Focusable="False"                                     HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"                                     VerticalAlignment="{TemplateBinding VerticalContentAlignment}"                                     Margin="{TemplateBinding Padding}"                                     RecognizesAccessKey="True"                                     SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>               </Border>               <ControlTemplate.Triggers>                   <Trigger Property="IsMouseOver" Value="True">                       <Setter Property="Background" TargetName="border" Value="{StaticResource Button.MouseOver.Control.Background}"/>                       <Setter Property="TextElement.Foreground" TargetName="contentPresenter" Value="{StaticResource Button.MouseOver.Control.Foreground}"/>                   </Trigger>                   <Trigger Property="IsPressed" Value="True">                       <Setter Property="Background" TargetName="border" Value="{StaticResource Button.Pressed.Control.Background}"/>                       <Setter Property="TextElement.Foreground" TargetName="contentPresenter" Value="{StaticResource Button.Pressed.Control.Foreground}"/>                   </Trigger>                   <Trigger Property="IsEnabled" Value="False">                       <Setter Property="Background" TargetName="border" Value="{StaticResource Button.Disabled.Control.Background}"/>                       <Setter Property="TextElement.Foreground" TargetName="contentPresenter" Value="{StaticResource Button.Disabled.Control.Foreground}"/>                   </Trigger>               </ControlTemplate.Triggers>           </ControlTemplate>       </Setter.Value>   </Setter> </Style>

Summary

  • Use TextElement.Foreground targeting the ContentPresenter in ControlTemplate.Triggers to update text color properly.
  • Do not manually declare Content="{TemplateBinding Content}" on a ContentPresenter inside a ContentControl template, as it disrupts built-in property inheritance.