Tuesday, 19 June 2018

Binding a FlexLayout to a Collection II

Previously, I explained how to extend the FlexLayout class with ItemsSource and ItemTemplate properties, so that it can bind to data stored in a collection. However, the resulting ExtendedFlexLayout class is only a minimally viable implementation as it omits a number of required features.

One of the missing features is that it doesn’t allow a DataTemplateSelector to choose a DataTemplate at runtime based on the value of a bound property. Instead it only permits a single defined DataTemplate to be used.

The purpose of this blog post is to further extend the ExtendedFlexLayout class so that it does allow a DataTemplateSelector to choose a DataTemplate at runtime. This enables scenarios such as the ExtendedFlexLayout binding to a collection of objects where the appearance of each object can be chosen at runtime by a data template selector returning a particular DataTemplate.

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

Adding DataTemplateSelector Support

As I explained previously, the ExtendedFlexLayout class has a CreateChildView method, which is called for each item in the collection that the ItemsSource property binds to, to load the DataTemplate referenced by the ItemTemplate property and set its binding context:

View CreateChildView(object item) { ItemTemplate.SetValue(BindableObject.BindingContextProperty, item); return (View)ItemTemplate.CreateContent(); }

This method can be extended to support a DataTemplateSelector choosing a DataTemplate at runtime:

View CreateChildView(object item) { if (ItemTemplate is DataTemplateSelector) { var dts = ItemTemplate as DataTemplateSelector; var itemTemplate = dts.SelectTemplate(item, null); itemTemplate.SetValue(BindableObject.BindingContextProperty, item); return (View)itemTemplate.CreateContent(); } else { ItemTemplate.SetValue(BindableObject.BindingContextProperty, item); return (View)ItemTemplate.CreateContent(); } }

This additional code checks if the ItemTemplate property references a class that inherits from DataTemplateSelector. If it is, it calls the DataTemplateSelector.SelectTemplate method to return the particular DataTemplate. Then, as before, the DataTemplate is loaded and its binding context is set.

Consuming the ExtendedFlexLayout

A data template selector is implemented by creating a class that inherits from DataTemplateSelector. The OnSelectTemplate method is then overridden to return a particular DataTemplate, as shown in the following code example:

public class MonkeyDataTemplateSelector : DataTemplateSelector { public DataTemplate NormalMonkeyTemplate { get; set; } public DataTemplate CynicalMonkeyTemplate { get; set; } protected override DataTemplate OnSelectTemplate(object item, BindableObject container) { return ((Monkey)item).Name.Contains("Face-Palm") ? CynicalMonkeyTemplate : NormalMonkeyTemplate; } }

Here, the OnSelectTemplate method returns the appropriate template based on the value of the Name property. The template to return is the template referenced by the NormalMonkeyTemplate property or the CynicalMonkeyTemplate, which are set when consuming the MonkeyDataTemplateSelector.

The MonkeyDataTemplateSelector is instantiated by declaring it as a resource:

<ContentPage.Resources> <Style x:Key="normalMonkeyFrame" TargetType="Frame"> <Setter Property="BackgroundColor" Value="LightYellow" /> <Setter Property="BorderColor" Value="Blue" /> <Setter Property="Margin" Value="10" /> <Setter Property="CornerRadius" Value="15" /> </Style> <Style x:Key="cynicalMonkeyFrame" TargetType="Frame"> <Setter Property="BackgroundColor" Value="Red" /> <Setter Property="BorderColor" Value="Black" /> <Setter Property="Margin" Value="10" /> <Setter Property="CornerRadius" Value="15" /> </Style> ... <DataTemplate x:Key="normalMonkeyTemplate"> <Frame WidthRequest="300" HeightRequest="480" Style="{StaticResource normalMonkeyFrame}"> ... </Frame> </DataTemplate> <DataTemplate x:Key="cynicalMonkeyTemplate"> <Frame WidthRequest="300" HeightRequest="480" Style="{StaticResource cynicalMonkeyFrame}"> ... </Frame> </DataTemplate> <local:MonkeyDataTemplateSelector x:Key="monkeyDataTemplateSelector" NormalMonkeyTemplate="{StaticResource normalMonkeyTemplate}" CynicalMonkeyTemplate="{StaticResource cynicalMonkeyTemplate}" /> </ContentPage.Resources>

The ResourceDictionary above defines two DataTemplate instances, and a MonkeyDataTemplateSelector instance. The MonkeyDataTemplateSelector instance sets its NormalMonkeyTemplate and CynicalMonkeyTemplate properties to the appropriate DataTemplate instances through the StaticResource markup extension. The two DataTemplates are largely identical, aside from setting different colours on the Frame instances.

The MonkeyDataTemplateSelector instance is consumed by assigning it to the ExtendedFlexLayout.ItemTemplate property:

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

At runtime, the MonkeyDataTemplateSelector.OnSelectTemplate method is called (by the ExtendedFlexLayout class via the DataTemplateSelector.SelectTemplate method call) for each of the items in the underlying collection, with the call passing the data object as the item parameter. The DataTemplate that is returned by the method is then applied to that object.

The following screenshots show the result of the ExtendedFlexLayout applying the MonkeyDataTemplateSelector to each object in the underlying collection:


Simulator Screen Shot - iPhone 8 - 2018-06-18 at 16.09.05Simulator Screen Shot - iPhone 8 - 2018-06-18 at 16.09.10Simulator Screen Shot - iPhone 8 - 2018-06-18 at 16.09.15

Any Monkey object that has a Name property that contains “Face-Palm” is displayed with a red background, as a warning to be wary of cynical monkeys, with the remaining objects being displayed with a a light yellow background.

Summary

This blog post has explained how to further extend the ExtendedFlexLayout class so that it allows a DataTemplateSelector to choose a DataTemplate at runtime. This enables scenarios such as the ExtendedFlexLayout 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. My next blog post will look at further extending the ExtendedFlexLayout class with additional functionality.

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

Thursday, 14 June 2018

Binding a FlexLayout to a Collection

In May we published a doc on the new FlexLayout control that’s present in Xamarin.Forms 3.0. FlexLayout is a versatile layout control that can arrange its children horizontally and vertically in a stack, and is also capable of wrapping its children if there are too many to fit in a single row or column. It also has options for orientation, alignment, and adapting to various screen sizes.

In the FlexLayout guide we outlined some common usage scenarios, with one being a catalog of items that are displayed horizontally, which are navigated through by swiping in the appropriate direction. Each item in the catalog is defined inline in XAML as children of the FlexLayout element. This is all very well for static catalog items that are defined by the developer, but what if the data is stored in a collection that’s populated from an external source such as a web service, or a database? This would require the FlexLayout to bind to a collection containing the data. However, this isn’t currently possible as there’s no ItemsSource property (or similar) on the FlexLayout class.

The purpose of this blog post is to demonstrate extending the FlexLayout with ItemsSource and ItemTemplate properties, so that it can bind to data stored in a collection. Note that the implementation will be a minimally viable implementation, rather than a production ready implementation. It demonstrates the simplest approach for binding a FlexLayout to a collection of items.

The sample the code in this blog post comes from can be found on GitHub.

Extending FlexLayout

The first step is to create a new class that inherits from FlexLayout, and add the required bindable properties and properties:

public class ExtendedFlexLayout : FlexLayout { public static readonly BindableProperty ItemsSourceProperty = BindableProperty.Create( nameof(ItemsSource), typeof(IEnumerable), typeof(ExtendedFlexLayout), propertyChanged: OnItemsSourceChanged); public static readonly BindableProperty ItemTemplateProperty = BindableProperty.Create( nameof(ItemTemplate), typeof(DataTemplate), typeof(ExtendedFlexLayout)); public IEnumerable ItemsSource { get { return (IEnumerable)GetValue(ItemsSourceProperty); } set { SetValue(ItemsSourceProperty, value); } } public DataTemplate ItemTemplate { get { return (DataTemplate)GetValue(ItemTemplateProperty); } set { SetValue(ItemTemplateProperty, value); } } ... }

The code above simply defines ItemsSource and ItemTemplate properties, along with matching BindableProperty versions of them. The ItemTemplate property will reference the DataTemplate to apply to each item in the collection referenced by the ItemsSource property. Note that the ItemsSourceProperty has a property changed handler defined, named OnItemsSourceChanged. This is where the templated items will be added to the ExtendedFlexLayout:

public class ExtendedFlexLayout : FlexLayout { ... static void OnItemsSourceChanged(BindableObject bindable, object oldVal, object newVal) { IEnumerable newValue = newVal as IEnumerable; var layout = (ExtendedFlexLayout)bindable; layout.Children.Clear(); if (newValue != null) { foreach (var item in newValue) { layout.Children.Add(layout.CreateChildView(item)); } } } View CreateChildView(object item) { ItemTemplate.SetValue(BindableObject.BindingContextProperty, item); return (View)ItemTemplate.CreateContent(); } }

The OnItemsSourceChanged method iterates through the collection referenced by the ItemsSource property, and calls the CreateChildView method for each item. This method sets the binding context of the ItemTemplate to the item, loads the DataTemplate referenced by the ItemTemplate property, and returns it to the OnItemsSourceChanged method, where the templated item is added as a child of the ExtendedFlexLayout.

Consuming the ExtendedFlexLayout

The ExtendedFlexLayout can be consumed in XAML as follows:

<ScrollView Orientation="Horizontal" Margin="0,20"> <local:ExtendedFlexLayout ItemsSource="{Binding Monkeys}"> <local:ExtendedFlexLayout.ItemTemplate> <DataTemplate> <Frame WidthRequest="300" HeightRequest="480"> <FlexLayout Direction="Column"> <Label Text="{Binding Name}" Style="{StaticResource headerLabel}" /> <Label Text="{Binding Description}" /> <Label Text="{Binding Trait1}" Margin="10,0,0,0" /> <Label Text="{Binding Trait2}" Margin="10,0,0,0" /> <Label Text="{Binding Trait3}" Margin="10,0,0,0" /> <Image Source="{Binding Image, Converter={StaticResource _stringToImageConverter}}" WidthRequest="180" HeightRequest="180" /> <Label FlexLayout.Grow="1" /> <Button /> </FlexLayout> </Frame> </DataTemplate> </local:ExtendedFlexLayout.ItemTemplate> </local:ExtendedFlexLayout> </ScrollView>

This ExtendedFlexLayout instance sets its ItemsSource property to a collection named Monkeys, which exists on the view model the page binds to. It also sets its ItemTemplate property to an inline DataTemplate that binds different views to different properties of each Monkey in the Monkeys collection. The result is, as per the FlexLayout guide, there are three items displayed that can be navigated through by swiping in the appropriate direction:

Simulator Screen Shot - iPhone 8 - 2018-06-14 at 11.57.59

The difference between the FlexLayout guide and this approach is that in the guide each item is declared inline in XAML. Here, each item comes from a collection that the ExtendedFlexLayout binds to. The advantage of this approach is that it allows the displayed data to be populated from an external source, such as a web service or a database.

Issues

As I previously mentioned, the ExtendedFlexLayout is a minimally viable implementation. While it works for the scenario outlined here, it’s quite limited:

  • It doesn’t respond to the bound collection changing at runtime (think ObservableCollection). Instead, the entire collection must be available when binding occurs.
  • It doesn’t respond to binding context changes. The binding context must be set when binding occurs, and can’t change.
  • It only permits a single defined DataTemplate to be used. It doesn’t allow a DataTemplateSelector to choose a DataTemplate at runtime based on the value of a bound property.
  • There’s no UI virtualisation. For large collections, the ExtendedFlexLayout could consume a lot of memory.

I’ll explore some of these issues in future blog posts.

Summary

This blog post has explained how to extend the FlexLayout with ItemsSource and ItemTemplate properties, so that it can bind to data stored in a collection. However, the ExtendedFlexLayout is currently a minimally viable implementation. My next blog post will look at extending the implementation with additional functionality.

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

Tuesday, 12 June 2018

Xamarin.Forms Behaviors: InvokeCommandAction and ConverterParameter

I previously mentioned that I’d published v1.4 of my Behaviors library, and that it included some new functionality. In my last blog post I looked at the SourceObject property that’s now present on the EventHandlerBehavior class. The final new item is the ConverterParameter property that now exists on the InvokeCommandAction class.

The InvokeCommandAction class executes a specified ICommand when invoked. It requires you to set a Command property to an ICommand instance, and CommandParameter and Converter properties can be optionally set. The CommandParameter property should be set to an object instance, with the Converter property being set to an instance of a class that implements IValueConverter. The ICommand specified in the Command property will then be executed when the “something” the parent behavior listens for occurs, with the CommandParameter and Converter values being used if specified.

Introducing the ConverterParameter Property

In v1.4 of the Behaviors library, the InvokeCommandAction class also has an optional ConverterParameter property, of type object. When this property is set, its value is passed to the Convert and ConvertBack methods of the IValueConverter implementation, as the parameter argument.

The following XAML shows an example of the InvokeCommandAction passing a parameter to the converter:

<ListView x:Name="listView" ItemsSource="{Binding People}"> <ListView.Behaviors> <behaviors:EventHandlerBehavior EventName="ItemSelected"> <behaviors:InvokeCommandAction Command="{Binding OutputAgeCommand}" Converter="{StaticResource SelectedItemConverter}" ConverterParameter="35" /> </behaviors:EventHandlerBehavior> </ListView.Behaviors> </ListView>

When the ListView.ItemSelected event fires, the OutputAgeCommand is executed. The InvokeCommandAction class expects to find the ICommand on the BindingContext of the attached object (and the BindingContext may have been set by a parent element). The Converter property of the InvokeCommandAction instance is set to the SelectedItemConverter instance, and the value of the ConverterParameter property is passed to the SelectedItemConverter instance as the parameter argument:

public class SelectedItemEventArgsToSelectedItemConverter : IValueConverter { public object Convert (object value, Type targetType, object parameter, CultureInfo culture) { var eventArgs = value as SelectedItemChangedEventArgs; var person = eventArgs.SelectedItem as Person; if (parameter != null) { int ageParam = int.Parse(parameter.ToString()); person = new Person(person.Name, person.Age, ageParam); } return person; } ... }

The converter returns the SelectedItem of the ListView from the SelectedItemChangedEventArgs. If the parameter argument contains data, it’s converted to an int and a new Person object is constructed that contains the int value of parameter. The InvokeCommandDemoPageViewModel then uses the Person object to update the AgeText property (which the UI binds to) to a message to be output:

void OutputAge (Person person) { if (person.AgeParameter > person.Age) AgeText = string.Format("{0} is {1}. That's younger than {2}.", person.Name, person.Age, person.AgeParameter); else AgeText = string.Format("{0} is {1}. That's older than {2}.", person.Name, person.Age, person.AgeParameter); OnPropertyChanged ("AgeText"); }

While this is a contrived example, it does illustrate how the ConverterParameter property can be used to pass a parameter to the value converter, that can be used in the conversion process.

Note that even if the instance of the value converter is shared among several data bindings, the ConverterParameter property can be different to perform different conversions.

Summary

In v1.4 of the Behaviors library, the InvokeCommandAction class also has an optional ConverterParameter property, of type object. When this property is set, its value is passed to the Convert and ConvertBack methods of the IValueConverter implementation, as the parameter argument.

The sample application that this code comes from can be downloaded from GitHub, and the Behaviors library can be found on NuGet.

Friday, 8 June 2018

Xamarin.Forms Behaviors: EventHandlerBehavior and the SourceObject

I previously mentioned that I’d published v1.4 of my Behaviors library, and that it included some new functionality. In my last blog post I looked at the GoToStateAction, which is the main item of new functionality. Another new item is the SourceObject property that now exists on the EventHandlerBehavior class.

Prior to this addition, to use the EventHandlerBehavior you’d set the EventName property to the event that you want the behavior to listen to, and the EventHandlerBehavior would automatically look for this event on the VisualElement the behavior is attached to. The following XAML shows an example of this syntax:

<ListView ItemsSource="{Binding People}"> <ListView.Behaviors> <behaviors:EventHandlerBehavior EventName="ItemSelected"> <behaviors:InvokeCommandAction Command="{Binding ItemSelectedCommand}" Converter="{StaticResource SelectedItemConverter}" /> <behaviors:InvokeCommandAction Command="{Binding OutputAgeCommand}" Converter="{StaticResource SelectedItemConverter}" /> </behaviors:EventHandlerBehavior> </ListView.Behaviors> </ListView>

Here, when the ListView.ItemSelected event fires, the ItemSelectedCommand and OutputAgeCommand are sequentially executed. To stress the point again, the EventHandlerBehavior automatically looks for the ItemSelected event on the VisualElement the behavior is attached to – in this case, the ListView.

Introducing the SourceObject Property

In v1.4 of the Behaviors library, the EventHandlerBehavior has the following properties:

  • EventName – the name of the event to listen to, which must exist on the SourceObject.
  • (optional) SourceObject – the object on which the behavior should listen for the event specified by the EventName property. If not provided, the behavior will default to listening for the event on the VisualElement the behavior is attached to.
  • Actions – one of more actions that should be executed in response to the event firing. However, this property is typically set indirectly by specifying the action instances (InvokeCommandAction, SetPropertyAction etc.) as children of the EventHandlerBehavior instance.

By making the SourceObject property optional, and by defaulting it to listening for the event on the VisualElement the behavior is attached to, backwards compatibility with existing codebases is ensured.

The following code example shows an example of specifying the SourceObject property:

<ListView x:Name="listView" ItemsSource="{Binding People}"> <ListView.Behaviors> <behaviors:EventHandlerBehavior EventName="ItemSelected"> <behaviors:InvokeCommandAction Command="{Binding ItemSelectedCommand}" Converter="{StaticResource SelectedItemConverter}" /> <behaviors:InvokeCommandAction Command="{Binding OutputAgeCommand}" Converter="{StaticResource SelectedItemConverter}" /> </behaviors:EventHandlerBehavior> </ListView.Behaviors> </ListView> <Label x:Name="itemSelectedLabel"> <Label.Behaviors> <behaviors:EventHandlerBehavior EventName="ItemSelected" SourceObject="{x:Reference listView}"> <behaviors:SetPropertyAction TargetObject="{x:Reference itemSelectedLabel}" PropertyName="Text" Value="Item selected in ListView." /> </behaviors:EventHandlerBehavior> </Label.Behaviors> </Label>

As before, when the ListView.ItemSelected event fires, the ItemSelectedCommand and OutputAgeCommand are sequentially executed. Here, because the first EventHandlerBehavior instance doesn’t specify a SourceObject, the behavior automatically looks for the ItemSelected event on the VisualElement the behavior is attached to – in this case, the ListView.

The EventHandlerBehavior instance that’s attached to the Label also listens for the ItemSelected event on the ListView, even though the behavior is attached to a Label. This is achieved by specifying the SourceObject as a reference to the ListView. Therefore, when the ListView.ItemSelected event fires, the SetPropertyAction instance updates the Label.Text property with the text specified by the Value property. Note that this example is completely contrived. It would be simpler to just move the SetPropertyAction instance to be a child of the first EventHandlerBehavior instance and remove the behavior instance from the Label (only one EventHandlerBehavior instance in the XAML then, instead of two). However, it illustrates that it’s now possible to specify an EventHandlerBehavior that listens for events on objects other than the VisualElement the behavior is attached to. This makes it possible to attach an EventHandlerBehavior instance to a VisualElement that the actions operate on, which can aid code readability.

Summary

The EventHandlerBehavior now has a SourceObject property that can be set to the object on which the behavior should listen for the event specified by the EventName property. If not provided, the EventHandlerBehavior will default to the VisualElement the behavior is attached to.

The sample application that this code comes from can be downloaded from GitHub, and the Behaviors library can be found on NuGet.