App Theming with Xamarin.Forms Control Templates

Avatar

David

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:

Teal-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:

Aqua-Screenshots

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:

Teal-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

Comments are closed.