Bindable Layouts in Xamarin.Forms
Bindable layouts enable any layout class that derives from the Layout<T>
class to generate its content by binding to a collection of items, with the option to set the appearance of each item with a DataTemplate
. Bindable layouts are provided by the BindableLayout
class, which exposes the following attached properties:
ItemsSource
– specifies the collection ofIEnumerable
items to be displayed by the layout.ItemTemplate
– specifies theDataTemplate
to apply to each item in the collection of items displayed by the layout.ItemTemplateSelector
– specifies theDataTemplateSelector
that will be used to choose aDataTemplate
for an item at runtime.
Note
The ItemTemplate
property takes precedence when both the ItemTemplate
and ItemTemplateSelector
properties are set.
In addition, the BindableLayout
class exposes the following bindable properties:
EmptyView
– specifies thestring
or view that will be displayed when theItemsSource
property isnull
, or when the collection specified by theItemsSource
property isnull
or empty. The default value isnull
.EmptyViewTemplate
– specifies theDataTemplate
that will be displayed when theItemsSource
property isnull
, or when the collection specified by theItemsSource
property isnull
or empty. The default value isnull
.
Note
The EmptyViewTemplate
property takes precedence when both the EmptyView
and EmptyViewTemplate
properties are set.
All of these properties can be attached to the AbsoluteLayout
, FlexLayout
, Grid
, RelativeLayout
, and StackLayout
classes, which all derive from the Layout<T>
class.
The Layout<T>
class exposes a Children
collection, to which the child elements of a layout are added. When the BindableLayout.ItemsSource
property is set to a collection of items and attached to a Layout<T>
-derived class, each item in the collection is added to the Layout<T>.Children
collection for display by the layout. The Layout<T>
-derived class will then update its child views when the underlying collection changes. For more information about the Xamarin.Forms layout cycle, see Creating a Custom Layout.
Bindable layouts should only be used when the collection of items to be displayed is small, and scrolling and selection isn't required. While scrolling can be provided by wrapping a bindable layout in a ScrollView
, this is not recommended as bindable layouts lack UI virtualization. When scrolling is required, a scrollable view that includes UI virtualization, such as ListView
or CollectionView
, should be used. Failure to observe this recommendation can lead to performance issues.
Important
While it's technically possible to attach a bindable layout to any layout class that derives from the Layout<T>
class, it's not always practical to do so, particularly for the AbsoluteLayout
, Grid
, and RelativeLayout
classes. For example, consider the scenario of wanting to display a collection of data in a Grid
using a bindable layout, where each item in the collection is an object containing multiple properties. Each row in the Grid
should display an object from the collection, with each column in the Grid
displaying one of the object's properties. Because the DataTemplate
for the bindable layout can only contain a single object, it's necessary for that object to be a layout class containing multiple views that each display one of the object's properties in a specific Grid
column. While this scenario can be realised with bindable layouts, it results in a parent Grid
containing a child Grid
for each item in the bound collection, which is a highly inefficient and problematic use of the Grid
layout.
Populate a bindable layout with data
A bindable layout is populated with data by setting its ItemsSource
property to any collection that implements IEnumerable
, and attaching it to a Layout<T>
-derived class:
<Grid BindableLayout.ItemsSource="{Binding Items}" />
The equivalent C# code is:
IEnumerable<string> items = ...;
var grid = new Grid();
BindableLayout.SetItemsSource(grid, items);
When the BindableLayout.ItemsSource
attached property is set on a layout, but the BindableLayout.ItemTemplate
attached property isn't set, every item in the IEnumerable
collection will be displayed by a Label
that's created by the BindableLayout
class.
Define item appearance
The appearance of each item in the bindable layout can be defined by setting the BindableLayout.ItemTemplate
attached property to a DataTemplate
:
<StackLayout BindableLayout.ItemsSource="{Binding User.TopFollowers}"
Orientation="Horizontal"
...>
<BindableLayout.ItemTemplate>
<DataTemplate>
<controls:CircleImage Source="{Binding}"
Aspect="AspectFill"
WidthRequest="44"
HeightRequest="44"
... />
</DataTemplate>
</BindableLayout.ItemTemplate>
</StackLayout>
The equivalent C# code is:
DataTemplate circleImageTemplate = ...;
var stackLayout = new StackLayout();
BindableLayout.SetItemsSource(stackLayout, viewModel.User.TopFollowers);
BindableLayout.SetItemTemplate(stackLayout, circleImageTemplate);
In this example, every item in the TopFollowers
collection will be displayed by a CircleImage
view defined in the DataTemplate
:
For more information about data templates, see Xamarin.Forms Data Templates.
Choose item appearance at runtime
The appearance of each item in the bindable layout can be chosen at runtime, based on the item value, by setting the BindableLayout.ItemTemplateSelector
attached property to a DataTemplateSelector
:
<FlexLayout BindableLayout.ItemsSource="{Binding User.FavoriteTech}"
BindableLayout.ItemTemplateSelector="{StaticResource TechItemTemplateSelector}"
... />
The equivalent C# code is:
DataTemplateSelector dataTemplateSelector = new TechItemTemplateSelector { ... };
var flexLayout = new FlexLayout();
BindableLayout.SetItemsSource(flexLayout, viewModel.User.FavoriteTech);
BindableLayout.SetItemTemplateSelector(flexLayout, dataTemplateSelector);
The DataTemplateSelector
used in the sample application is shown in the following example:
public class TechItemTemplateSelector : DataTemplateSelector
{
public DataTemplate DefaultTemplate { get; set; }
public DataTemplate XamarinFormsTemplate { get; set; }
protected override DataTemplate OnSelectTemplate(object item, BindableObject container)
{
return (string)item == "Xamarin.Forms" ? XamarinFormsTemplate : DefaultTemplate;
}
}
The TechItemTemplateSelector
class defines DefaultTemplate
and XamarinFormsTemplate
DataTemplate
properties that are set to different data templates. The OnSelectTemplate
method returns the XamarinFormsTemplate
, which displays an item in dark red with a heart next to it, when the item is equal to "Xamarin.Forms". When the item isn't equal to "Xamarin.Forms", the OnSelectTemplate
method returns the DefaultTemplate
, which displays an item using the default color of a Label
:
For more information about data template selectors, see Creating a Xamarin.Forms DataTemplateSelector.
Display a string when data is unavailable
The EmptyView
property can be set to a string, which will be displayed by a Label
when the ItemsSource
property is null
, or when the collection specified by the ItemsSource
property is null
or empty. The following XAML shows an example of this scenario:
<StackLayout BindableLayout.ItemsSource="{Binding UserWithoutAchievements.Achievements}"
BindableLayout.EmptyView="No achievements">
...
</StackLayout>
The result is that when the data bound collection is null
, the string set as the EmptyView
property value is displayed:
Display views when data is unavailable
The EmptyView
property can be set to a view, which will be displayed when the ItemsSource
property is null
, or when the collection specified by the ItemsSource
property is null
or empty. This can be a single view, or a view that contains multiple child views. The following XAML example shows the EmptyView
property set to a view that contains multiple child views:
<StackLayout BindableLayout.ItemsSource="{Binding UserWithoutAchievements.Achievements}">
<BindableLayout.EmptyView>
<StackLayout>
<Label Text="None."
FontAttributes="Italic"
FontSize="{StaticResource smallTextSize}" />
<Label Text="Try harder and return later?"
FontAttributes="Italic"
FontSize="{StaticResource smallTextSize}" />
</StackLayout>
</BindableLayout.EmptyView>
...
</StackLayout>
The result is that when the data bound collection is null
, the StackLayout
and its child views are displayed.
Similarly, the EmptyViewTemplate
can be set to a DataTemplate
, which will be displayed when the ItemsSource
property is null
, or when the collection specified by the ItemsSource
property is null
or empty. The DataTemplate
can contain a single view, or a view that contains multiple child views. In addition, the BindingContext
of the EmptyViewTemplate
will be inherited from the BindingContext
of the BindableLayout
. The following XAML example shows the EmptyViewTemplate
property set to a DataTemplate
that contains a single view:
<StackLayout BindableLayout.ItemsSource="{Binding UserWithoutAchievements.Achievements}">
<BindableLayout.EmptyViewTemplate>
<DataTemplate>
<Label Text="{Binding Source={x:Reference usernameLabel}, Path=Text, StringFormat='{0} has no achievements.'}" />
</DataTemplate>
</BindableLayout.EmptyViewTemplate>
...
</StackLayout>
The result is that when the data bound collection is null
, the Label
in the DataTemplate
is displayed:
Note
The EmptyViewTemplate
property can't be set via a DataTemplateSelector
.
Choose an EmptyView at runtime
Views that will be displayed as an EmptyView
when data is unavailable, can be defined as ContentView
objects in a ResourceDictionary
. The EmptyView
property can then be set to a specific ContentView
, based on some business logic, at runtime. The following XAML shows an example of this scenario:
<ContentPage ...>
<ContentPage.Resources>
...
<ContentView x:Key="BasicEmptyView">
<StackLayout>
<Label Text="No achievements."
FontSize="14" />
</StackLayout>
</ContentView>
<ContentView x:Key="AdvancedEmptyView">
<StackLayout>
<Label Text="None."
FontAttributes="Italic"
FontSize="14" />
<Label Text="Try harder and return later?"
FontAttributes="Italic"
FontSize="14" />
</StackLayout>
</ContentView>
</ContentPage.Resources>
<StackLayout>
...
<Switch Toggled="OnEmptyViewSwitchToggled" />
<StackLayout x:Name="stackLayout"
BindableLayout.ItemsSource="{Binding UserWithoutAchievements.Achievements}">
...
</StackLayout>
</StackLayout>
</ContentPage>
The XAML defines two ContentView
objects in the page-level ResourceDictionary
, with the Switch
object controlling which ContentView
object will be set as the EmptyView
property value. When the Switch
is toggled, the OnEmptyViewSwitchToggled
event handler executes the ToggleEmptyView
method:
void ToggleEmptyView(bool isToggled)
{
object view = isToggled ? Resources["BasicEmptyView"] : Resources["AdvancedEmptyView"];
BindableLayout.SetEmptyView(stackLayout, view);
}
The ToggleEmptyView
method sets the EmptyView
property of the stackLayout
object to one of the two ContentView
objects stored in the ResourceDictionary
, based on the value of the Switch.IsToggled
property. Then, when the data bound collection is null
, the ContentView
object set as the EmptyView
property is displayed: