Monday, 2 July 2018

Binding a Collection to a FlexLayout III

Previously, I explained how to extend the ExtendedFlexLayout class so that it allows a DataTemplateSelector to choose a DataTemplate at runtime. This enables scenarios such as the ExtendedFlexLayout class binding to a collection of objects where the appearance of each object can be chosen at runtime by the data template selector returning a particular DataTemplate.

Any items in the collection the ExtendedFlexLayout.ItemsSource property binds will be displayed on screen. However, if the collection changes at runtime, the changes aren’t displayed. It’s useful to think about how a collection can change at runtime - items could be added, moved, removed, or replaced. In addition, the collection could be cleared.

In this blog post I’ll examine how to extend the ExtendedFlexLayout class so that it can bind to a collection that changes at runtime, by responding to collection change notifications, with the changes being reflected on screen. In this implementation, collection changes will only be reflected on screen when items are added to the collection, or removed from the collection.

The sample this code comes from can be found on GitHub.

Adding CollectionChanged Support

The key to responding to a bound collection changing at runtime is subscribing to the CollectionChanged event of the collection, and then updating the Children property of the ExtendedFlexLayout class in response. An event handler for the collection’s CollectionChanged event can be registered in the OnItemsSourceChanged method, which is executed whenever the ItemsSource property changes:

1 static void OnItemsSourceChanged(BindableObject bindable, object oldVal, object newVal) 2 { 3 IEnumerable newValue = newVal as IEnumerable; 4 var layout = (ExtendedFlexLayout)bindable; 5 6 var observableCollection = newValue as INotifyCollectionChanged; 7 if (observableCollection != null) 8 { 9 observableCollection.CollectionChanged += layout.OnItemsSourceCollectionChanged; 10 } 11 12 layout.Children.Clear(); 13 if (newValue != null) 14 { 15 foreach (var item in newValue) 16 { 17 layout.Children.Add(layout.CreateChildView(item)); 18 } 19 } 20 }

Lines 6-10 contain the new code that’s been added to this method. This code casts the bound collection to INotifyCollectionChanged, and then registers an event handler for the CollectionChanged even. This event fires whenever an item is added, removed, changed, or moved in the bound collection, or when the collection is cleared. Other than that, the code in the method hasn’t changed.

The OnItemsSourceCollectionChanged method, which handles the CollectionChanged event, is shown in the following example:

void OnItemsSourceCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { if (e.OldItems != null) { // Item(s) replaced, moved, or removed - only remove processed here. Children.RemoveAt(e.OldStartingIndex); } if (e.NewItems != null) { // Item(s) added. for (int i = 0; i < e.NewItems.Count; i++) { var item = e.NewItems[i]; var view = CreateChildView(item); Children.Insert(e.NewStartingIndex + i, view); } } }

This event handler uses the received NotifyCollectionChangedEventArgs argument to determine the action to perform. The event argument data includes an Action property, which identifies whether an item has been added, moved, removed, replaced, or whether the collection has been cleared. However, it’s not always necessary to use this property to handle the collection changing. Instead, the OldItems and NewItems properties can be examined to partly determine the action required.

The OldItems property contains the list of items that have been replaced, removed, or moved within the collection, and the OldStartingIndex property returns the index at which the replace, remove, or move action took place. The NewItems property contains a list of items added to the collection, with the NewStartingIndex property returning the index at which the change occurred.

In the code above, if the OldItems property contains an item, it’s removed from the ExtendedFlexLayout.Children collection. If the NewItems property contains items, they are added to the ExtendedFlexLayout.Children collection. This is achieved by calling the CreateChildView method, which loads the DataTemplate and sets its binding context, with the resulting view being inserted into the Children collection.

Therefore, the ExtendedFlexLayout class only handles items being added to and removed from the bound collection at runtime (and not items being moved, replaced, or the collection being cleared), but it can easily be extended to handle additional actions by examining the Action property. In fact, I will return to this property in my next post.

Consuming the ExtendedFlexLayout

The ExtendedFlexLayout is consumed exactly as before:

<local:ExtendedFlexLayout ItemsSource="{Binding Monkeys}" ItemTemplate="{StaticResource monkeyDataTemplateSelector}" />

However, the ItemsSource property now binds to an ObservableCollection. When the MainPageViewModel is constructed, an instance of the ObservableCollection is created and a single Monkey is added to it. The ObservableCollection can then be manipulated by buttons on the UI (adding and removing Monkeys from the collection), with the ExtendedFlexLayout being updated in response:


Simulator Screen Shot - iPhone 8 - 2018-06-22 at 13.56.07

Summary

This blog post has explained how to further extend the ExtendedFlexLayout class so that it can bind to collections that change at runtime, with the changes being reflected on screen. While the ExtendedFlexLayout class only handles items being added to and removed from the bound collection at runtime (and not items being moved, replaced, or the collection being emptied), it can easily be extended to handle additional actions by examining the Action property.

The sample this code comes from can be found on GitHub.

No comments:

Post a Comment