How to use a Grid with Bindable Layout (more than one column)

6.9k Views Asked by At

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.

5

There are 5 best solutions below

1
Oleg Kaliuzhnyi On BEST ANSWER

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>
0
Bruno Caceiro On

Checking this issue, seems that what you are trying to accomplish can't be done with a Bindable Layout using a Grid as a Element.

The documentation isn't as clear as it should, nevertheless.

1
Cherry Bu - MSFT On

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.

From Bindable Layouts, we can see:

While it's technically possible to attach a bindable layout to any layout class that derives from the Layout 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.

If you still want to more column, I suggest you can use StackLayout, it can also meet your requirement.

<StackLayout BindableLayout.ItemsSource="{Binding persons}">
        <BindableLayout.ItemTemplate>
            <DataTemplate>
                <StackLayout Orientation="Horizontal">
                    <Label Text="{Binding name}" />
                    <Label Text="{Binding age}" />
                </StackLayout>
            </DataTemplate>
        </BindableLayout.ItemTemplate>
    </StackLayout>
0
Vicent Camarena On

You can subscribe to BindingContextChanged event and configure then all the items. You have to configure the grid definitions programatically after the event.

4
Stephen Quan On

This can be solved by property binding to Grid.Row and Grid.Column as well, e.g.

<Grid BindableLayout.ItemsSource="{Binding Items}">
    <BindableLayout.ItemTemplate>
        <DataTemplate>
            <Label Grid.Row="{Binding MyGridRowProperty}"
                   Grid.Column="{Binding MyGridColumnProperty}"
                   Text="{Binding MyProperty}"/>
        </DataTemplate>
    </BindableLayout.ItemTemplate>
</Grid>