In Xamarin.Forms 3.5 Microsoft introduced us to bindable layouts which can be used to dynamically fill layouts (e.g. StackLayout
, Grid
, etc.).
To use this in a grid with a single column is pretty straightforward:
<Grid BindableLayout.ItemsSource="{Binding Items}">
<BindableLayout.ItemTemplate>
<DataTemplate>
<Label Text="{Binding MyProperty}"/>
</DataTemplate>
</BindableLayout.ItemTemplate>
</Grid>
Now my question is how this can be used to populate a grid with more than one column due to the fact that DataTemplate
only allows one view as content. Sure I could but another Grid
in it but this would totally nullify the value of bindable layout in a Grid
.
I've created a behaviour which provides view index as a bindable property. In my case I have always the same amount of items, so I can setup ColumnDefinitions/RowDefinitions and bind Grid.Column/Row to the behavior's Index property.
public sealed class IndexProviderBehavior : Behavior<View>
{
View _view;
public static readonly BindableProperty IndexProperty =
BindableProperty.Create(nameof(Index), typeof(int), typeof(IndexProviderBehavior),
defaultBindingMode: BindingMode.OneWayToSource);
public int Index
{
get => (int)GetValue(IndexProperty);
set => SetValue(IndexProperty, value);
}
protected override void OnAttachedTo(View bindable)
{
base.OnAttachedTo(bindable);
_view = bindable;
bindable.ParentChanged += OnParentChanged;
SetupIndex();
}
protected override void OnDetachingFrom(View bindable)
{
base.OnDetachingFrom(bindable);
_view.ParentChanged -= OnParentChanged;
_view = null;
}
private void OnParentChanged(object sender, EventArgs e)
{
SetupIndex();
}
private void SetupIndex()
{
if (_view.Parent is Layout layout)
{
Index = layout.Children.IndexOf(_view);
return;
}
Index = 0;
}
}
Usage:
<Grid
ColumnDefinitions="*,*,*,*,*,*,*"
BindableLayout.ItemsSource="{Binding Items}">
<BindableLayout.ItemTemplate>
<DataTemplate>
<Label
Text="{Binding .}"
Grid.Column="{Binding Index, Source={x:Reference indexBehavior}}"
>
<Label.Behaviors>
<behaviors:IndexProviderBehavior x:Name="indexBehavior" />
</Label.Behaviors>
</Label>
</DataTemplate>
</BindableLayout.ItemTemplate>
</Grid>