Using InheritanceBehavior to Aid Theme Development

29 Aug 2011

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?

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:

<ButtonStyle="{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:

<DataGridStyle="{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:

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.

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!