xamarin forms bindable wrappanel

A wrappanel is something XAML developers are very familiar with. It’s a panel that orders its children either horizontally or vertically but when the children reach the border, the panel wraps to the next row or column.

Xamarin Forms currently does not have a default implementation for something similar so I set out to find one online. To my surprise it took quite some time to find something (guess I’m spoiled by the awesome toolkits we have as Windows app devs), eventually I found the WrapLayout.

The WrapLayout works but had a disadvantage, it didn’t have an ItemsSource or DataTemplate property but I did have a solid foundation, so I went to expand the implementation.

I started by creating a bindable property for itemssource and one for item template. In MS XAML we would use Dependency properties, in Xamarin XAML it’s called BindableProperty.

   1: /// <summary>
   2: /// Backing Storage for the Spacing property
   3: /// </summary>
   4: public static readonly BindableProperty ItemTemplateProperty =
   5:     BindableProperty.Create<AwesomeWrappanel, DataTemplate>(w => w.ItemTemplate, null,
   6:         propertyChanged: (bindable, oldvalue, newvalue) => ((AwesomeWrappanel)bindable).OnSizeChanged());
   7:  
   8: /// <summary>
   9: /// Spacing added between elements (both directions)
  10: /// </summary>
  11: /// <value>The spacing.</value>
  12: public DataTemplate ItemTemplate
  13: {
  14:     get { return (DataTemplate)GetValue(ItemTemplateProperty); }
  15:     set { SetValue(ItemTemplateProperty, value); }
  16: }
  17:  
  18: /// <summary>
  19: /// Backing Storage for the Spacing property
  20: /// </summary>
  21: public static readonly BindableProperty ItemsSourceProperty =
  22:     BindableProperty.Create<AwesomeWrappanel, IEnumerable>(w => w.ItemsSource, null,
  23:         propertyChanged: ItemsSource_OnPropertyChanged);
  24:  
  25: /// <summary>
  26: /// Spacing added between elements (both directions)
  27: /// </summary>
  28: /// <value>The spacing.</value>
  29: public IEnumerable ItemsSource
  30: {
  31:     get { return (IEnumerable)GetValue(ItemsSourceProperty); }
  32:     set { SetValue(ItemsSourceProperty, value); }
  33: }

ItemTemplate is a property of type DataTemplate, ItemSource is an IEnumerable (the non-generic version). So far so good, next up is adding some logic to add the itemsource items as children of the Layout. Since the wrappanel has Layout<T> as a base there is no itemsource from the underlying control.

   1: private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs args)
   2: {
   3:     foreach (object item in args.NewItems)
   4:     {
   5:         var child = ItemTemplate.CreateContent() as View;
   6:         if (child == null)
   7:             return;
   8:  
   9:         child.BindingContext = item;
  10:         Children.Add(child);
  11:     }
  12: }

We’re going to respond to collection changed events, hoping that whoever uses the control binds to a collection that implements INotifyCollectionChanged, like the infamous ObservableCollection<T>. We’re creating a View from the DataTemplate for every newly added item, set the BindingContext to the item itself and add it to the Children collection. The binding framework will take care of all the rest.

One step left, we need to add an event handler for the CollectionChanged event. We do this in the property changed callback of the ItemSource bindable property

   1: private static void ItemsSource_OnPropertyChanged(BindableObject bindable, IEnumerable oldvalue, IEnumerable newvalue)
   2: {
   3:     if (oldvalue != null)
   4:     {
   5:         var coll = (INotifyCollectionChanged)oldvalue;
   6:         // Unsubscribe from CollectionChanged on the old collection
   7:         coll.CollectionChanged -= ItemsSource_OnItemChanged;
   8:     }
   9:  
  10:     if (newvalue != null)
  11:     {
  12:         var coll = (INotifyCollectionChanged)newvalue;
  13:         // Subscribe to CollectionChanged on the new collection
  14:         coll.CollectionChanged += ItemsSource_OnItemChanged;
  15:     }
  16: }

If there’s a previous instance we disconnect from its event, afterwards we try to cast the itemsource to INotifyCollectionChanged, if that succeeds we know there’s a collection changed event we can hook into. One problem here, this is all static but the properties we need to use in the event handler are not so we need to find a way to get from static to non-static code, events to the rescue!

I added a static event, cool thing about those is that the event handlers can be non-static.

   1: private static event EventHandler<NotifyCollectionChangedEventArgs> _collectionChanged;

Used the constructor to hook up the handler

   1: public AwesomeWrappanel()
   2: {
   3:     _collectionChanged += OnCollectionChanged;
   4: }

And that’s it! The handler method is the one we discussed a bit earlier, where the items in the itemsource are converted into Children elements.

Here’s how to use it in XAML

   1: <controls:AwesomeWrappanel ItemsSource="{Binding Persons}" Orientation="Horizontal">
   2:     <controls:AwesomeWrappanel.ItemTemplate>
   3:         <DataTemplate>
   4:             <StackLayout BackgroundColor="Red">
   5:                 <Label Text="{Binding Name}" />
   6:                 <Label Text="{Binding Age}" />
   7:             </StackLayout>
   8:         </DataTemplate>
   9:     </controls:AwesomeWrappanel.ItemTemplate>
  10: </controls:AwesomeWrappanel>

A quick screenshot of the result:

A demo project can be found on my OneDrive or see this GitHub Gist for the Wrappanel class.

Happy coding!

This is an imported post. It was imported from my old blog using an automated tool and may contain formatting errors and/or broken images.

Leave a Comment