Controls have different properties, such as BackgroundColor
and TextColor
, that can define aspects of the control’s appearance. These properties can be set using styles, which can be changed at runtime in order to implement basic theming. We have already looked at theming applications with styles. However, styles don’t maintain a clean separation between the appearance of a page and its content, and the changes that can be made by setting such properties are limited.
Control templates provide a clean separation between the appearance of a page and its content, therefore enabling the creation of pages that can easily be themed. For example, an application may contain application level control templates that provide a dark theme and a light theme. Each ContentPage
in the application can be themed by applying one of the control templates, without changing the content being displayed by the page. In addition, the themes provided by control templates aren’t limited to changing the properties of controls. They can also change the controls used to implement the theme.
In this blog post, I’m going to explore using control templates to theme and re-theme application pages at runtime.
Introduction to Control Templates
A ControlTemplate
specifies the appearance of a page or view and contains a root layout and, within that layout, the controls that implement the template. Typically, a ControlTemplate
will utilize a ContentPresenter
to mark where the content to be displayed by the page or view will appear. The page or view that consumes the ControlTemplate
will then define content to be displayed by the ContentPresenter
.
A ControlTemplate
can be applied to the following types by setting their ControlTemplate
properties:
ContentPage
ContentView
TemplatedPage
TemplatedViewÂ
When a ControlTemplate
is created and assigned to these types, any existing appearance is replaced with the appearance defined in the ControlTemplate
.
Control templates created in XAML are defined in a ResourceDictionary
that’s assigned to the Resources
collection of a page, or more typically to the Resources
collection of the application. Control templates lower in the view hierarchy take precedence over those defined higher up. For example, a ControlTemplate
named DarkTheme
that’s defined at the page level will take precedence over an identically named template defined at the application level.
Creating a ControlTemplate
To define a ControlTemplate
at the application level, a ResourceDictionary
must be added to the App
class, as shown in the following code example from the sample application:
<Application ... x:Class="SimpleTheme.App"> <Application.Resources> <ResourceDictionary> <ControlTemplate x:Key="TealTemplate"> <Grid> ... <BoxView ... /> <Label Text="Control Template Demo App" TextColor="White" VerticalOptions="Center" ... /> <ContentPresenter ... /> <BoxView Color="Teal" ... /> <Label Text="(c) Xamarin 2016" TextColor="White" VerticalOptions="Center" ... /> </Grid> </ControlTemplate> <ControlTemplate x:Key="AquaTemplate"> ... </ControlTemplate> </ResourceDictionary> </Application.Resources> </Application>
Each ControlTemplate
instance is created as a reusable object in a ResourceDictionary
. This is achieved by giving each declaration a unique x:Key
attribute, which provides it with a descriptive key in the ResourceDictionary
.
The following code example shows a ContentPage
applying the TealTemplate
to the ContentView
:
<ContentPage ... x:Class="SimpleTheme.HomePage"> <ContentView x:Name="contentView" Padding="0,20,0,0" ControlTemplate="{StaticResource TealTemplate}"> <StackLayout VerticalOptions="CenterAndExpand"> <Label Text="Welcome to the app!" HorizontalOptions="Center" /> <Button Text="Change Theme" Clicked="OnButtonClicked" /> </StackLayout> </ContentView> </ContentPage>
The TealTemplate
is assigned to the ContentView.ControlTemplate
property by using the StaticResource
markup extension. The ContentView.Content
property is set to a StackLayout
that defines the content to be displayed on the ContentPage
. This content will be displayed by the ContentPresenter
contained in the TealTemplate
. This results in the appearance shown in the following screenshots:
Re-theming an Application at Runtime
Clicking the Change Theme button executes the OnButtonClicked
method, which is shown in the following code example from the sample application:
void OnButtonClicked (object sender, EventArgs e) { originalTemplate = !originalTemplate; contentView.ControlTemplate = (originalTemplate) ? tealTemplate : aquaTemplate; }
This method replaces the active ControlTemplate
instance with the alternative ControlTemplate
instance, resulting in the following screenshot:
Extending a ControlTemplate with a TemplateBinding
Template bindings allow controls in a control template to data bind to public properties, enabling property values on controls in the control template to be easily changed. A TemplateBinding
is similar to an existing Binding
, except that the source of a TemplateBinding
is always automatically set to the parent of the target view that owns the control template.
In XAML, a TemplateBinding
is created using the TemplateBinding
markup extension, as demonstrated in the following code example from the sample application:
<ControlTemplate x:Key="TealTemplate"> <Grid> ... <Label Text="{TemplateBinding Parent.HeaderText}" ... /> ... <Label Text="{TemplateBinding Parent.FooterText}" ... /> </Grid> </ControlTemplate>
Rather than set the Label.Text
properties to static text, the properties use template bindings to bind to bindable properties on the parent of the target view that owns the ControlTemplate
. However, note that the template bindings bind to Parent.HeaderText
and Parent.FooterText
, rather than HeaderText
and FooterText
. This is because in the sample application, the bindable properties are defined on the grandparent of the target view, rather than the parent, as demonstrated in the following code example from the sample application:
public static readonly BindableProperty HeaderTextProperty = BindableProperty.Create ("HeaderText", typeof(string), typeof(HomePage), "Control Template Demo App"); public static readonly BindableProperty FooterTextProperty = BindableProperty.Create ("FooterText", typeof(string), typeof(HomePage), "(c) Xamarin 2016"); public string HeaderText { get { return (string)GetValue (HeaderTextProperty); } } public string FooterText { get { return (string)GetValue (FooterTextProperty); } }
This results in the appearance shown in the following screenshots:
While these screenshots are identical to earlier screenshots, here the two Label
instances in the control template have their Text
properties set through a template binding, rather than being hard coded inside the template.
Wrapping Up
Control templates provide a clean separation between the appearance of a page and its content, therefore enabling the creation of pages that can easily be themed and re-themed at runtime. Control templates can use template bindings to allow controls in the template to data bind to public properties, allowing property values on controls in the control template to be easily changed.
For more information about control templates, see our Control Templates Guide.
0 comments