Using InheritanceBehavior to Aid Theme Development

If you’ve ever put together a custom theme for WPF, you’ll know the value of comparing your theme to the system theme. You can quickly spot things that you’ve missed, or compare and contrast behavior. However, if your theme targets controls by type rather than by key (and most themes should), how do you ensure your theme isn’t applied to a given control? That is, how do you ensure a given control inherits the system theme regardless of the application-defined themes?

Suppose you’re implementing a style for buttons. It looks like this:

<Style TargetType="Button">
    <Setter Property="Background">
        <Setter.Value>
            <LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
                <GradientStop Offset="0" Color="#5f9ea0"/>
                <GradientStop Offset="0.5" Color="#3f7e80"/>
                <GradientStop Offset="0.5" Color="#7fbec0"/>
                <GradientStop Offset="1" Color="#1f5e80"/>
            </LinearGradientBrush>
        </Setter.Value>
    </Setter>
    <Setter Property="Foreground" Value="White"/>
    <Setter Property="FontFamily" Value="Bauhaus 93"/>
    <Setter Property="FontSize" Value="12pt"/>
    <Setter Property="FontWeight" Value="Normal"/>
</Style>

You want to put together a little test control that shows your themed Button against a system-themed Button:

<StackPanel> 
    <Button>System Theme</Button> 
    <Button>Application Theme</Button> 
</StackPanel>

But that won’t work - both buttons will inherit your theme. In this case, you can simply tell the second Button not to apply your Style, thus ensuring it inherits the system-defined one:

<Button Style="{x:Null}">System Theme</Button>

But what if you’re theming something far more complex, like the DataGrid control? It has child controls (DataGridCell, DataGridRow, DataGridColumnHeader etc), each with their own style. How can you stop all those child controls from inheriting the styles provided by your theme? Well, sometimes the parent control (DataGrid in this case) will expose properties that allow you to override the styles for child controls:

<DataGrid Style="{x:Null}" CellStyle="{x:Null}" RowStyle="{x:Null}" .../>

But this is tedious, error-prone, and not all controls will expose such properties. What, then, is the diligent WPF journeyman to do?

The best way around this problem I’ve found is to make use of the little-known FrameworkElement.InheritanceBehavior property. This protected property specifies how resource and property inheritance lookups should behave from any FrameworkElement. We can define a simple control with an inheritance behavior to meet our needs as follows:

public sealed class UseSystemTheme : ContentControl 
{ 
    public UseSystemTheme() 
    { 
        this.InheritanceBehavior = System.Windows.InheritanceBehavior.SkipToThemeNow; 
    } 
}

Then, to prevent a control from inheriting our themed Style, we can simply wrap it in a UseSystemTheme. Going back to our Button example, it would look like:

<StackPanel> 
    <local:UseSystemTheme> 
        <Button>System Theme</Button> 
    </local:UseSystemTheme> 
    <Button>Application Theme</Button> 
</StackPanel>

Now it’s easy to ensure we’re comparing the application theme against the system theme. Moreover, you can use the same approach regardless of how complex the contained elements are. So it’ll work whether you’re styling a Button, a DataGrid, or whatever.

Screenshot

NOTE: I actually made my UseSystemTheme control a little more complex than presented here. I had it override its default Background value and set it to SystemColors.WindowBrush. Then I had to provide a template for it because, by default, a ContentControl does not honor its Background property. These changes ensure that every UseSystemTheme instance has the default brush as its background instead of inheriting whatever else you’ve set in your theme. It’s the child of the UseSystemTheme instance whose resource lookup is affected by InheritanceBehavior, not the UseSystemTheme instance itself!

comments powered by Disqus