xamarinlayoutxamarin.formsgridbindable

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


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.


Solution

  • 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>