Control Themes
Control themes build upon Styles to create switchable themes for controls. Control themes are analogous to Styles in WPF/UWP, though their mechanism is slightly different.
Because control themes are based on styles, it is important to understand the Avalonia styling system first.
Introduction
Before Avalonia 11, control themes were created using standard styles. However, this approach had a fundamental problem: once a style was applied to a control, there was no way to remove it. Consequently, if you wanted to change the theme for a specific instance of a control or a section of the user interface (UI), the only option was to apply a second theme to the control and hope that it would override all the properties set in the original theme.
The solution for this was introduced in Avalonia 11 in the form of Control Themes.
Control themes are themselves styles, but with some important differences:
- Control themes don't have a selector: instead they have a
TargetType
property which describes the control that they target - Control themes are stored in a
ResourceDictionary
instead of aStyles
collection - Control themes are assigned to a control by setting the
Theme
property, usually using the{StaticResource}
markup extension
Control themes are typically applied to templated (lookless) controls, but they can actually be applied to any control. However, for non-templated controls, it is often more convenient to use standard styles instead.
Example: Round Button
The following example shows a simple Button
theme which displays a button with an ellipse background with a 90's Geocities aesthetic:
<Application xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="AvaloniaApplication.App">
<Application.Styles>
<FluentTheme />
</Application.Styles>
<Application.Resources>
<ControlTheme x:Key="EllipseButton" TargetType="Button">
<Setter Property="Background" Value="Blue"/>
<Setter Property="Foreground" Value="Yellow"/>
<Setter Property="Padding" Value="8"/>
<Setter Property="Template">
<ControlTemplate>
<Panel>
<Ellipse Fill="{TemplateBinding Background}"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"/>
<ContentPresenter x:Name="PART_ContentPresenter"
Content="{TemplateBinding Content}"
Margin="{TemplateBinding Padding}"/>
</Panel>
</ControlTemplate>
</Setter>
</ControlTheme>
</Application.Resources>
</Application>
<Window xmlns="https://github.com/avaloniaui"
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
x:Class="Sandbox.MainWindow">
<Button Theme="{StaticResource EllipseButton}"
HorizontalAlignment="Center"
VerticalAlignment="Center">
Hello World!
</Button>
</Window>
Interaction in Control Themes
Like standard styles, control themes support nested styles which can be used to add interactions such as pointer-over and pressed states.
Example: Round Button Hover State
Using nested styles we can make our button change color when the pointer is hovered over it:
<Application xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="AvaloniaApplication.App">
<Application.Styles>
<FluentTheme />
</Application.Styles>
<Application.Resources>
<ControlTheme x:Key="EllipseButton" TargetType="Button">
<Setter Property="Background" Value="Blue"/>
<Setter Property="Foreground" Value="Yellow"/>
<Setter Property="Padding" Value="8"/>
<Setter Property="Template">
<ControlTemplate>
<Panel>
<Ellipse Fill="{TemplateBinding Background}"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"/>
<ContentPresenter x:Name="PART_ContentPresenter"
Content="{TemplateBinding Content}"
Margin="{TemplateBinding Padding}"/>
</Panel>
</ControlTemplate>
</Setter>
<Style Selector="^:pointerover">
<Setter Property="Background" Value="Red"/>
<Setter Property="Foreground" Value="White"/>
</Style>
</ControlTheme>
</Application.Resources>
</Application>
Control Theme Lookup
There a two ways in which a control theme can be found:
- If the control's
Theme
property is set, then that control theme will be used; otherwise - Avalonia will search the upwards through the logical tree for a
ControlTheme
resource with anx:Key
which matches the control's style key
If you're having trouble getting Avalonia to find your theme, make sure it's returning a style key which matches the x:Key
and TargetType
of your control theme
In effect this means that you have two choices for how to define your control theme:
- If you want the control theme to apply to all instances of the control then use an
{x:Type}
as the resource key. For example<ControlTheme x:Key="{x:Type Button}" TargetType="Button">
- If you want the control theme to be applied to selected instances of the control then use anything else as the resource key and look up this resource using
{StaticResource}
. Commonly this key will be astring
Notice that this means that only a single control theme can be applied to a control at any one time.
Example: Make all the Buttons Round
We can apply our new control theme to all of the buttons in the application by simply changing the x:Key
of the control theme to match the Button
type.
<Application xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="AvaloniaApplication.App">
<Application.Styles>
<FluentTheme />
</Application.Styles>
<Application.Resources>
<ControlTheme x:Key="{x:Type Button}" TargetType="Button">
<Setter Property="Background" Value="Blue"/>
<Setter Property="Foreground" Value="Yellow"/>
<Setter Property="Padding" Value="8"/>
<Setter Property="Template">
<ControlTemplate>
<Panel>
<Ellipse Fill="{TemplateBinding Background}"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"/>
<ContentPresenter x:Name="PART_ContentPresenter"
Content="{TemplateBinding Content}"
Margin="{TemplateBinding Padding}"/>
</Panel>
</ControlTemplate>
</Setter>
<Style Selector="^:pointerover">
<Setter Property="Background" Value="Red"/>
<Setter Property="Foreground" Value="White"/>
</Style>
</ControlTheme>
</Application.Resources>
</Application>
TargetType
The ControlTheme.TargetType
property specifies the type to which setter properties apply. If you don't specify a TargetType
, you must qualify the properties in your Setter objects with a class name by using the syntax Property="ClassName.Property"
. For example, instead of setting Property="FontSize", you must set Property to TextBlock.FontSize
or Control.FontSize
.
Additional Resources
- The ButtonCustomize sample has an
WinClassicButtonTheme
- You can see the control themes for the built-in Avalonia controls here: