Using XAML in Xamarin.Forms – Part 2: Accessing Static Data

The first article in the Using XAML in Xamarin.Forms series, looked at how XAML increases the legibility of the user interface code over C#, how to set both member and attribute properties, and how to setup event handlers for user interface events.

In this article, I want to explore how to access data from XAML. This article will cover static data, or data that does not change once the page is displayed, and the next will cover dynamic data and data binding.

Why Access Static Data?

Typical XAML is littered with what appear to be hardcoded string constants all over the place. Why is it worth your time to break with that pattern to learn how to access static data rather than just type the value in?

You don't need me to tell you that hardcoding constants in multiple files isn't the best of practices. Life is a whole lot easier when the value of the constant changes at design-time and that constant is actually defined in one spot, rather than referenced all over the place.

XAML also becomes more legible and the scoping of constants can become very fine-grained too.

In addition, referencing constants from XAML rather than hardcoding them into the control's definition, allows for some interesting uses of data binding.

Finally, since XAML is a language used in the user interface layer, accessing static data defined as a constant from elsewhere is a great way to start building up a consistent look, or style, to your app.

StaticExtension Class

One of the easiest ways of accessing static data from a XAML file is with the StaticExtension class. The static extension class is used to access a static member, be it a constant, static property or field, or an enumeration of an object. It then returns the value of that static member.

For example, the RecipeNameLabel field of the following class:

public static class RecipeUIConstants
{
    public static string RecipeNameLabel = "Recipe Name";
}

Would be accessed in XAML as the following:

<Label Text="{x:Static local:RecipeUIConstants.RecipeNameLabel}" />

(Note that the local: XML namespace would be set to reference the CLR namespace of the RecipeUIConstants class at the very top of the XAML file.)

The usage then is to set the XAML property to the StaticExtenstion class. The StaticExtension class takes the format of {x:Static followed by the path to get at the static member. In this case the XML namespace of local followed by the class name and field name.

The StaticExtension class is not limited to only returning strings however, and it can be used to build up a more interesting UI as in the following example.

public static class RecipeUIConstants
{
    public static string RecipeNameLabel = "Recipe Name";
    public static string CookTimeLabel = "Cook Time";
    public static string IngredientsLabel = "Ingredients";
    public static string DirectionsLabel = "Directions";
    public static string NumberOfServingsLabel = "Number of Servings";

    public static Thickness PickerMargin = new Thickness(15, 0);

    public static AllServingOptions NumberOfServingsOptions = new AllServingOptions
    {
        new ServingOption { Description = "Individual", Servings = 1 },
        new ServingOption { Description = "Family Sized", Servings = 4 },
        new ServingOption { Description = "Buffet", Servings = 12 }
    };
}

public class AllServingOptions : List<ServingOption>
{
    public AllServingOptions(params ServingOption[] args)
    {
        this.AddRange(args);
    }

    public AllServingOptions() { }
}

public class ServingOption
{
    public string Description { get; set; }
    public int Servings { get; set; }
}

The RecipeUIConstants class has been expanded to include new fields – including one with a type of Thickness and another with a type of AllServingOptions : List<ServingOption>.

These can all be accessed in the following way to build up a data entry page:

<?xml version="1.0" encoding="utf-8"?>
<ContentPage 
    xmlns="http://xamarin.com/schemas/2014/forms" 
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" 
    xmlns:local="clr-namespace:Xaml2" 
    x:Class="Xaml2.Xaml2Page"
    Title="XAML Demo">
    <TableView Intent="Form">
        <TableView.Root>
            <TableSection Title="Enter Data">
                <EntryCell Label="{x:Static local:RecipeUIConstants.RecipeNameLabel}" />
                <EntryCell Label="{x:Static local:RecipeUIConstants.CookTimeLabel}" />
                <EntryCell Label="{x:Static local:RecipeUIConstants.IngredientsLabel}" />
                <EntryCell Label="{x:Static local:RecipeUIConstants.DirectionsLabel}" />
                <ViewCell>
                    <StackLayout Orientation="Horizontal" Margin="{x:Static local:RecipeUIConstants.PickerMargin}">
                        <Label Text="{x:Static local:RecipeUIConstants.NumberOfServingsLabel}" VerticalOptions="Center" />
                        <Picker VerticalOptions="Center" HorizontalOptions="EndAndExpand" 
                            ItemsSource="{x:Static local:RecipeUIConstants.NumberOfServingsOptions}"
                            ItemDisplayBinding="{Binding Description}" />
                    </StackLayout>
                </ViewCell>
            </TableSection>
        </TableView.Root>
    </TableView>
</ContentPage>

The Labels of the EntryCells are all set to the various strings, but something interesting is going on in the ViewCell that contains the Picker control.

First off, notice the StackLayout's Margin property is set to a value corresponding to a Thickness, thus demonstrating that the StaticExtension class can do more than return string values.

Secondly, the Picker is bound to an object that inherits from List<ServingOption> property. That's pretty neat! The ItemsSource can be set to a statically declared collection.

The running app with the picker shown looks like this:

That's great, but this is an article about XAML, and those constants were declared in C#. Let's see how we can do the same thing, but declaring the constants in XAML.

Resource Dictionaries

Besides being stored in in C# files, static values can be stored in XAML, inside what is known as a ResourceDictionary.

A ResourceDictionary is a Dictionary<string, object> class and is accessed through the Resources property of any Xamarin.Forms object which inherits from VisualElement.

That means the Resources property is available on most controls in Xamarin.Forms. And what's more – a child control can access anything in its parent's Resources ResourceDictionary.

For example, a every control on a page will have access to objects in the ResourceDictionary declared in the ContentPage.Resources property.

Defining a ResourceDictionary will look like the following:

<ContentPage.Resources>
    <ResourceDictionary>
        <x:String x:Key="RecipeNameLabel">Recipe Name</x:String>
        <x:String x:Key="CookTimeLabel">Cook Time</x:String>
        <x:String x:Key="IngredientsLabel">Ingredients</x:String>
        <x:String x:Key="DirectionsLabel">Directions</x:String>
        <x:String x:Key="NumberOfServingsLabel">Number of Servings</x:String>
        <Thickness x:Key="PickerMargin">15,0</Thickness>
    </ResourceDictionary>
</ContentPage.Resources>

Each static value is declared via its data type followed by a key which it will serve as its key in the Dictionary. Finally, the value of the constant is given.

To access values defined in a ResourceDictionary you need to use the StaticResource markup extension.

<EntryCell Label="{StaticResource RecipeNameLabel}"/>
<EntryCell Label="{StaticResource CookTimeLabel}" />
<EntryCell Label="{StaticResource IngredientsLabel}" />
<EntryCell Label="{StaticResource DirectionsLabel}" />
<ViewCell>
    <StackLayout Orientation="Horizontal" Margin="{StaticResource PickerMargin}">
        <Label Text="{StaticResource NumberOfServingsLabel}" VerticalOptions="Center" />
        <Picker VerticalOptions="Center" HorizontalOptions="EndAndExpand" 
            ItemsSource="{x:Static local:RecipeUIConstants.NumberOfServingsOptions}"
            ItemDisplayBinding="{Binding Description}" />
    </StackLayout>
</ViewCell>

The StaticResource is invoked within curly braces followed by the name of the key in the ResourceDictionary you wish to retrieve the value from. You then set the {StaticResource KeyName} to the property you wish to receive its value.

The XAML is also a bit more legible, a side-benefit of using the ResourceDictionary.

Because values from ResourceDictionary's have a scope, it is easy to define global level constants in the App class, and more finely grained constants only where you need them, at the Page level or even further down, such as at the individual Layout level.

Thus ResourceDictionary then not only gives you the ability to define static values in XAML, leading to more legible code, but also gives more control of where the values apply to.

Everything still looks the same in the app. However, you may have noticed that there was one StaticExtension class holdover in the Picker.ItemSource property.

<Picker VerticalOptions="Center" HorizontalOptions="EndAndExpand" 
    ItemsSource="{x:Static local:RecipeUIConstants.NumberOfServingsOptions}"
    ItemDisplayBinding="{Binding Description}" />

That's because creating complex objects is a bit more difficult, but it can be done, and the next section will show you how.

Creating Objects From XAML

It is entirely possible to create complex objects within XAML. Both parameterless and parametered constructors can be invoked.

Parameterless constructors are easy, all one needs to do is reference the class you wish to instantiate, and the object will be created. For example:

<local:ServingOption />

Will create a ServingOption class – although its properties will not be set to any values. However, we can also invoke constructors with parameters in XAML, and that's with using the <x:Arguments> keyword.

If we modify the ServingOption class to look like the code below:

public class ServingOption
{
    public ServingOption(string description, int servings)
    {
        Description = description;
        Servings = servings;
    }

    public string Description { get; set; }
    public int Servings { get; set; }
}

Then the following syntax would be used to create that class in XAML:

<local:ServingOption x:Key="Buffet">
    <x:Arguments>
        <x:String>Buffet</x:String>
        <x:Int32>12</x:Int32>
    </x:Arguments>
</local:ServingOption>

The values for the constructor get passed in within the <x:Arguments> tags. Now the entire page can be created within XAML – no object instantiation needed from anywhere else in the code:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:Xaml2"
             xmlns:generic="clr-namespace:System.Collections;assembly=mscorlib"
             x:Class="Xaml2.ConstructorPage">

    <ContentPage.Resources>
        <ResourceDictionary>
            <x:String x:Key="RecipeNameLabel">Recipe Name</x:String>
            <x:String x:Key="CookTimeLabel">Cook Time</x:String>
            <x:String x:Key="IngredientsLabel">Ingredients</x:String>
            <x:String x:Key="DirectionsLabel">Directions</x:String>
            <x:String x:Key="NumberOfServingsLabel">Number of Servings</x:String>
            <Thickness x:Key="PickerMargin">15,0</Thickness>

            <local:AllServingOptions x:Key="servingOptions">
                <x:Arguments>
                    <x:Array x:Key="args" Type="{x:Type local:ServingOption}">
                        
                        <local:ServingOption x:Key="Individual">
                            <x:Arguments>
                                <x:String>Individual</x:String>
                                <x:Int32>1</x:Int32>
                            </x:Arguments>
                        </local:ServingOption>  
                        
                        <local:ServingOption x:Key="FamilySized">
                            <x:Arguments>
                                <x:String>Family Sized</x:String>
                                <x:Int32>4</x:Int32>
                            </x:Arguments>
                        </local:ServingOption>
                        
                        <local:ServingOption x:Key="Buffet">
                            <x:Arguments>
                                <x:String>Buffet</x:String>
                                <x:Int32>12</x:Int32>
                            </x:Arguments>
                        </local:ServingOption>
                        
                    </x:Array>
                </x:Arguments>
            </local:AllServingOptions>
            
        </ResourceDictionary>
    </ContentPage.Resources>

    <TableView Intent="Form">
        <TableView.Root>
            <TableSection Title="Enter Data">
                <EntryCell Label="{StaticResource RecipeNameLabel}"/>
                <EntryCell Label="{StaticResource CookTimeLabel}" />
                <EntryCell Label="{StaticResource IngredientsLabel}" />
                <EntryCell Label="{StaticResource DirectionsLabel}" />
                <ViewCell>
                    <StackLayout Orientation="Horizontal" Margin="{StaticResource PickerMargin}">
                        <Label Text="{StaticResource NumberOfServingsLabel}" VerticalOptions="Center" />
                        <Picker VerticalOptions="Center" HorizontalOptions="EndAndExpand" 
                            ItemsSource="{StaticResource servingOptions}"
                            ItemDisplayBinding="{Binding Description}" />
                    </StackLayout>
                </ViewCell>
            </TableSection>
        </TableView.Root>
    </TableView>
</ContentPage>

The interesting thing to look at here is what is happening within the servingOptions key. It is first creating an AllServingOptions object, using the constructor which accepts an array of parameters. Then it is creating individual ServingOption objects to be sent into that array.

All the variable initialization does make the page long, and one would most likely put the servingOptions key into the App class, but the point is that you can pass static data to properties in XAML.

Summary

Accessing static data from XAML can be accomplished in several ways.

The first is to simply hardcode the value in. However, the downsides to this are apparent as soon as keeping the code consistent or refactoring takes place and should be avoided. The second is to use the StaticExtension class. This class accesses statically defined fields, constants, and enumerations from with XAML. The third way to access static data is the ResourceDictionary. You can place and access the same type of data from a ResourceDictionary that you can from a StaticExtension, except all of the values are initialized from XAML. Finally, you can even initialize objects that have constructors which take parameters.

Related content:

Comments