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