Skip to content

Instantly share code, notes, and snippets.

@NVentimiglia
Last active October 31, 2021 21:06
Show Gist options
  • Save NVentimiglia/2723411428cdbb72fac6 to your computer and use it in GitHub Desktop.
Save NVentimiglia/2723411428cdbb72fac6 to your computer and use it in GitHub Desktop.
Need a Items control or a repeater for Xamarin ? Here you go. Bind to observable collections, define your data template and enjoy.
// MIT License
// Nicholas Ventimiglia
// 2016-9-19
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Linq;
using System.Windows.Input;
using Xamarin.Forms;
/// <summary>
/// For repeated content without a automatic scroll view. Supports DataTemplates, Horizontal and Vertical layouts !
/// </summary>
/// <remarks>
/// Warning TODO NO Visualization or Paging! Handle this in your view model.
/// </remarks>
public class ItemsStack : StackLayout
{
#region BindAble
public static readonly BindableProperty ItemsSourceProperty = BindableProperty.Create<ItemsStack, IEnumerable>(p => p.ItemsSource, default(IEnumerable<object>), BindingMode.TwoWay, null, ItemsSourceChanged);
public static readonly BindableProperty SelectedItemProperty = BindableProperty.Create<ItemsStack, object>(p => p.SelectedItem, default(object), BindingMode.TwoWay, null, OnSelectedItemChanged);
public static readonly BindableProperty ItemTemplateProperty = BindableProperty.Create<ItemsStack, DataTemplate>(p => p.ItemTemplate, default(DataTemplate));
public event EventHandler<SelectedItemChangedEventArgs> SelectedItemChanged;
public IEnumerable ItemsSource
{
get { return (IEnumerable)GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
public object SelectedItem
{
get { return GetValue(SelectedItemProperty); }
set { SetValue(SelectedItemProperty, value); }
}
public DataTemplate ItemTemplate
{
get { return (DataTemplate)GetValue(ItemTemplateProperty); }
set { SetValue(ItemTemplateProperty, value); }
}
private static void ItemsSourceChanged(BindableObject bindable, IEnumerable oldValue, IEnumerable newValue)
{
var itemsLayout = (ItemsStack)bindable;
itemsLayout.SetItems();
}
private static void OnSelectedItemChanged(BindableObject bindable, object oldValue, object newValue)
{
var itemsView = (ItemsStack)bindable;
if (newValue == oldValue)
return;
itemsView.SetSelectedItem(newValue);
}
#endregion
#region item rendering
protected readonly ICommand ItemSelectedCommand;
protected virtual void SetItems()
{
Children.Clear();
if (ItemsSource == null)
{
ObservableSource = null;
return;
}
foreach (var item in ItemsSource)
Children.Add(GetItemView(item));
var isObs = ItemsSource.GetType().IsGenericType && ItemsSource.GetType().GetGenericTypeDefinition() == typeof(ObservableCollection<>);
if (isObs)
{
ObservableSource = new ObservableCollection<object>(ItemsSource.Cast<object>());
}
}
protected virtual View GetItemView(object item)
{
var content = ItemTemplate.CreateContent();
var view = content as View;
if (view == null)
return null;
view.BindingContext = item;
var gesture = new TapGestureRecognizer
{
Command = ItemSelectedCommand,
CommandParameter = item
};
AddGesture(view, gesture);
return view;
}
protected void AddGesture(View view, TapGestureRecognizer gesture)
{
view.GestureRecognizers.Add(gesture);
var layout = view as Layout<View>;
if (layout == null)
return;
foreach (var child in layout.Children)
AddGesture(child, gesture);
}
protected virtual void SetSelectedItem(object selectedItem)
{
var handler = SelectedItemChanged;
if (handler != null)
handler(this, new SelectedItemChangedEventArgs(selectedItem));
}
ObservableCollection<object> _observableSource;
protected ObservableCollection<object> ObservableSource
{
get { return _observableSource; }
set
{
if (_observableSource != null)
{
_observableSource.CollectionChanged -= CollectionChanged;
}
_observableSource = value;
if (_observableSource != null)
{
_observableSource.CollectionChanged += CollectionChanged;
}
}
}
private void CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
{
int index = e.NewStartingIndex;
foreach (var item in e.NewItems)
Children.Insert(index++, GetItemView(item));
}
break;
case NotifyCollectionChangedAction.Move:
{
var item = ObservableSource[e.OldStartingIndex];
Children.RemoveAt(e.OldStartingIndex);
Children.Insert(e.NewStartingIndex, GetItemView(item));
}
break;
case NotifyCollectionChangedAction.Remove:
{
Children.RemoveAt(e.OldStartingIndex);
}
break;
case NotifyCollectionChangedAction.Replace:
{
Children.RemoveAt(e.OldStartingIndex);
Children.Insert(e.NewStartingIndex, GetItemView(ObservableSource[e.NewStartingIndex]));
}
break;
case NotifyCollectionChangedAction.Reset:
Children.Clear();
foreach (var item in ItemsSource)
Children.Add(GetItemView(item));
break;
}
}
#endregion
public ItemsStack()
{
ItemSelectedCommand = new Command<object>(item =>
{
SelectedItem = item;
});
}
}
@thefallman
Copy link

Great job! Many thanks! I'm wondering, why so simple but powerful solution is still not part of the framework?

@Lexor7
Copy link

Lexor7 commented Aug 15, 2017

thanks.. helpfully..

@Jsrc1990
Copy link

Thanks sirs. it's possible to say that you have saved my life in a way...

@barryculhane
Copy link

barryculhane commented Aug 22, 2017

@eric-wieser and @fschwiet, would one of you be willing to post a complete gist with your revisions? When I try to apply your fixes to the original gist, it's not compiling. Thanks in advance!

@bmasis
Copy link

bmasis commented Aug 25, 2017

@eric-wieser error in ObservableSource = (IObservableReadOnlyCollection)o;
@fschwiet in setItem where do you get newValue?

@luismts
Copy link

luismts commented Sep 2, 2017

@bmasis +1

@CalvinFong
Copy link

@bmasis @barryculhane @luismts
Chuck this in SetItems (and you can remove ObservableSource). Should allow CollectionChanged to trigger (my issue at least was that it wasn't triggering for adding/removing elements from my ObservableCollection).

var itemsSourceINotifyCollectionChanged = ItemsSource as INotifyCollectionChanged;
if (itemsSourceINotifyCollectionChanged != null) {
	itemsSourceINotifyCollectionChanged.CollectionChanged += CollectionChanged;
}

@veamitkumar
Copy link

veamitkumar commented Nov 24, 2017

when i am using this itemStack , it does not have scrolling functionality and when i put that in scroll it does not scroll .

@pcdus
Copy link

pcdus commented Jan 25, 2018

I encounter an problem on this line:
var isObs = ItemsSource.GetType().IsGenericType && ItemsSource.GetType().GetGenericTypeDefinition() == typeof(ObservableCollection<>);

I get the following error:

'Type' does not contain a definition for 'IsGenericType' and no extension method 'IsGenericType' accepting a first argument of type 'Type' could be found (are you missing a using directive or an assembly reference?)

Any idea?

@jamsoft
Copy link

jamsoft commented Sep 26, 2018

None of this joins up, the original class and all the edits are incomplete unfortunately. Writing my own, thanks anyway.

@ItsBluee
Copy link

ItsBluee commented Feb 5, 2020

I uses a modified version of it for some extra feature, it was working all good until I upgraded my Xamarin.Forms version.
I gets null pointer exception over var content = ItemTemplate.CreateContent();
Anyone got the same issue or someone got a fix for it?

previous XF V - 2.3
current XF V - 4.2 (Forcefully needed to upgrade it cuz of iOS13 iPad issue with masterPage)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment