5

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.

Christoph Mett
  • 369
  • 3
  • 16
  • So, how is that "pretty straightforward" even with single Grid column? How can you set view Grid.Row attribute in Data template for sequential source items? All your Labels overlaps in first row! – Arvis Mar 05 '22 at 11:16

5 Answers5

2

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>
Cherry Bu - MSFT
  • 10,160
  • 1
  • 10
  • 16
  • 3
    that will not align items evenly if you want to have something like Grid with ColummDefinitions="*,*" – Emil Oct 22 '20 at 19:49
1

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>
  • I don't know exactly but you could maybe even set the Grid.Column/Grid.Row from directly from the behavior (like `Grid.ColumnProperty.SetValue` or such), but just as a proof-of-concept this already is good – Christoph Mett Aug 24 '23 at 17:43
0

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.

Bruno Caceiro
  • 7,035
  • 1
  • 26
  • 45
0

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

0

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>
Stephen Quan
  • 21,481
  • 4
  • 88
  • 75
  • Which would require the bound class to explicitly contain properties for row and column, but then, yes. – Christoph Mett Jun 19 '23 at 10:00
  • I mean that's the point about `Grid` right? You want to be able to specify where the components are going to appear right? If not, then, it kinda says `Grid` wasn't the component for you. You have `FlexLayout`+`BindableLayout` and let the system decide. Similarly with `CollectionView`, `ListView` and so forth. A clear use case would define one over another, e.g. I think `Grid` would be most appropriate if you're implementing a board game where each delegate actually needs to contain an X, Y for each piece. – Stephen Quan Jun 19 '23 at 23:06
  • That's the point of a `Grid`, yes, but not exactly the point of a `BindableLayout`, as already described in the other answers. In the fewest of cases one has a class that contains properties for row and column, and most of the time one does not even want those to be in the class that's used in the `BindableLayout.ItemsSource`. Long story short, what's actually needed is not possible with a `Grid` in Xamarin, and no, neither FlexLayout, nor CollectionView/ListView would suit that use-case. – Christoph Mett Jun 20 '23 at 20:19
  • If you do not want them in the class, it is possible (1) to derive the index of any item from your item source, (2) to declare a ValueConverter that converts such index to appropriate Row or Column values for you on the fly. – Stephen Quan Jun 20 '23 at 23:07