2
votes

I'm really new to Xamarin and Xamarin forms and I need some help.

I have a StackLayout to which I want to add items dynamically from my ViewModel. Problem is I can't seem to bind the content of the StackLayout to my ViewModel's StackLayout.

this is my xaml code in my view

<StackLayout/>

I want something like

<StackLayout Content="{Binding MainStackLayout}"/>

I have a StackLayout already setup in my ViewModel like this

public StackLayout MainStackLayout;
3

3 Answers

14
votes

You have to write a UI component.

using Xamarin.Forms;

using System.Collections.Specialized;
using System.ComponentModel;

class BindableStackLayout : StackLayout
{
    public static readonly BindableProperty ItemsProperty =
        BindableProperty.Create(nameof(Items), typeof(ObservableCollection<View>), typeof(BindableStackLayout), null,
            propertyChanged: (b, o, n) =>
            {
                (n as ObservableCollection<View>).CollectionChanged += (coll, arg) =>
                {
                    switch (arg.Action)
                    {
                        case NotifyCollectionChangedAction.Add:
                            foreach (var v in arg.NewItems)
                                (b as BindableStackLayout).Children.Add((View)v);
                            break;
                        case NotifyCollectionChangedAction.Remove:
                            foreach (var v in arg.NewItems)
                                (b as BindableStackLayout).Children.Remove((View)v);
                            break;
                        case NotifyCollectionChangedAction.Move:
                            //Do your stuff
                            break;
                        case NotifyCollectionChangedAction.Replace:
                            //Do your stuff
                            break;
                    }
                };
            });


    public ObservableCollection<View> Items
    {
        get { return (ObservableCollection<View>)GetValue(ItemsProperty); }
        set { SetValue(ItemsProperty, value); }
    }
}
2
votes

want to have bindable itemsource for your stacklayout with different sizes i made custom contentview will help you to achieve it :)

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Xamarin.Forms;
/*
this class will help you to have bindable children with different sizes for the stacklayout with scrollview 
in you xaml add 
    <UIControls:SAStackLayout ItemsSource="{Binding YourDataSource}"  Orientation="Horizontal">
                            <DataTemplate>
                                <Grid>
                                    <Label Text="{Binding Name}" Margin="15,0" HorizontalOptions="Center" VerticalOptions="Center"    FontSize="Small"  VerticalTextAlignment="Center" HorizontalTextAlignment="Center" TextColor="White"/>
                                </Grid>
                            </DataTemplate>
   </UIControls:SAStackLayout>

 */
namespace Shop.UI.Controls
{
    [ContentProperty("ItemContent")]
    public class SAStackLayout : ContentView
    {
        private ScrollView _scrollview;
        private StackLayout _stacklayout { get; set; }
        public SAStackLayout()
        {
            _stacklayout = new StackLayout();
            _scrollview = new ScrollView()
            {
                Content = _stacklayout
            };
            Content = _scrollview;
        }
        public static readonly BindableProperty ItemContentProperty = BindableProperty.Create("ItemContent", typeof(DataTemplate), typeof(SAStackLayout), default(ElementTemplate));

        public DataTemplate ItemContent
        {
            get { return (DataTemplate)GetValue(ItemContentProperty); }
            set { SetValue(ItemContentProperty, value); }
        }


        private ScrollOrientation _scrollOrientation;
        public ScrollOrientation Orientation
        {
            get
            {
                return _scrollOrientation;
            }
            set
            {
                _scrollOrientation = value;
                _stacklayout.Orientation = value == ScrollOrientation.Horizontal ? StackOrientation.Horizontal : StackOrientation.Vertical;
                _scrollview.Orientation = value;
            }
        }

        public static readonly BindableProperty ItemsSourceProperty = BindableProperty.Create("ItemsSource", typeof(IEnumerable), typeof(SAStackLayout), default(IEnumerable), propertyChanged: GetEnumerator);
        public IEnumerable ItemsSource
        {
            get { return (IEnumerable)GetValue(ItemsSourceProperty); }
            set { SetValue(ItemsSourceProperty, value); }
        }

        private static void GetEnumerator(BindableObject bindable, object oldValue, object newValue)
        {
            foreach (object child in (newValue as IEnumerable))
            {
                View view = (View)(bindable as SAStackLayout).ItemContent.CreateContent();
                view.BindingContext = child;
                (bindable as SAStackLayout)._stacklayout.Children.Add(view);
            }
        }
    }
}

download source from here

2
votes

Just use a BindableLayout: https://docs.microsoft.com/en-us/xamarin/xamarin-forms/user-interface/layouts/bindable-layouts

<StackLayout BindableLayout.ItemsSource="{Binding Items}">
    <BindableLayout.ItemTemplate>
        <DataTemplate>
            <Label Text="{Binding Title}" />
        </DataTemplate>
    </BindableLayout.ItemTemplate>
</StackLayout>

It supports ItemsSource, ItemTemplate and ItemTemplateSelector.