Monday, 23 July 2018

Binding a Collection to a FlexLayout IV

Previously, I explained how to 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, I mentioned that it could easily be extended to handle additional actions by examining the Action property.

In this blog post, I’ll examine how to extend the ExtendedFlexLayout class so that it can respond to the bound collection being cleared at runtime. The sample this code comes from can be found on GitHub.

Clearing the Collection

The ExtendedFlexLayout can respond to the bound collection being cleared at runtime in the handler for the CollectionChanged event:

void OnItemsSourceCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { if (e.Action == NotifyCollectionChangedAction.Reset) { // Items cleared Children.Clear(); } if (e.OldItems != null) { // Items removed 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); } } }

At the top of the event handler, the received NotifyCollectionChangedEventArgs are used to determine whether to clear the ExtendedFlexLayout control. 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. If the bound collection has been cleared, the Action property will be set to NotifyCollectionChangedAction.Reset and the control responds by clearing the Children collection. When the Children collection has been cleared, both the OldItems (the list of items that have been replaced, removed, or moved within the collection) and NewItems (the list of items that have been added to the collection) properties will be null, so the code for adding and removing items won’t be executed on this invocation of the event handler.

Summary

This blog post has explained how to further extend the ExtendedFlexLayout class so that it can respond to the bound collection being cleared at runtime. This is achieved by clearing the Children collection of the control when the Action property of the handler that responds to the CollectionChanged event is equal to NotifyCollectionChangedAction.Reset.

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

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.

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.

Thursday, 31 May 2018

Xamarin.Forms Behaviors: GoToStateAction

Previously, I mentioned that I’d published v1.4 of my Behaviors library, and that it included some new functionality. The main item of new functionality is a GoToStateAction class, which represents an action that will transition a VisualElement to a specified VisualState when triggered. It requires you to set a TargetObject property to the VisualElement that contains the VisualState, and a StateName property to the name of the VisualState to be transitioned to.

In this blog post, I’ll explain how to use the GoToStateAction class with both the EventHandlerBehavior and the DataChangedBehavior.

Transitioning to a VisualState when an Event Fires

The following code example shows an example of using the EventHandlerBehavior and GoToStateAction classes to transition a VisualElement to a specified VisualState when an event fires:

<StackLayout> <Label Text="Goto VisualState when Event Fires" FontAttributes="Bold" HorizontalOptions="Center" /> <Label Text="Select an item in the Picker:" /> <Picker x:Name="picker" Title="Colours"> <Picker.Items> <x:String>Red</x:String> <x:String>Blue</x:String> <x:String>Green</x:String> </Picker.Items> <Picker.Behaviors> <behaviors:EventHandlerBehavior EventName="SelectedIndexChanged"> <behaviors:GoToStateAction StateName="PickerItemSelected" TargetObject="{x:Reference stackLayout}" /> </behaviors:EventHandlerBehavior> </Picker.Behaviors> </Picker> <Label Text="Selecting an item in the Picker will enable content below." /> </StackLayout> <local:Separator /> <StackLayout x:Name="stackLayout" IsVisible="false"> <VisualStateManager.VisualStateGroups> <VisualStateGroup Name="PickerStates"> <VisualState Name="PickerItemSelected"> <VisualState.Setters> <Setter Property="IsVisible" Value="true" /> </VisualState.Setters> </VisualState> <VisualState Name="None" /> </VisualStateGroup> </VisualStateManager.VisualStateGroups> <Label Text="Goto VisualState when Data Changes" FontAttributes="Bold" HorizontalOptions="Center" /> ... </StackLayout>

When the user selects an item in the Picker, the SelectedIndexChanged event fires. The EventHandlerBehavior listens for this event to occur, and executes the GoToStateAction in response. The GoToStateAction causes the TargetObject (stackLayout) to transition to the StateName (PickerItemSelected). In this contrived example, this causes all of the views in the StackLayout named stackLayout to become visible.

Transitioning to a VisualState when Data Changes

The following code example shows an example of using the DataChangedBehavior and GoToStateAction classes to transition a VisualElement to a specified VisualState when data changes:

<Entry x:Name="entry" FontSize="Large" Margin="30, 0, 0, 0"> <Entry.Behaviors> <behaviors:DataChangedBehavior Binding="{Binding Path=Text, Source={x:Reference entry}}" ComparisonCondition="NotEqual" Value="Xamarin"> <behaviors:GoToStateAction StateName="Invalid" TargetObject="{x:Reference helpLabel}" /> <behaviors:GoToStateAction StateName="Invalid" TargetObject="{x:Reference submitButton}" /> </behaviors:DataChangedBehavior> <behaviors:DataChangedBehavior Binding="{Binding Path=Text, Source={x:Reference entry}}" ComparisonCondition="Equal" Value="Xamarin"> <behaviors:GoToStateAction StateName="Valid" TargetObject="{x:Reference helpLabel}" /> <behaviors:GoToStateAction StateName="Valid" TargetObject="{x:Reference submitButton}" /> </behaviors:DataChangedBehavior> </Entry.Behaviors> </Entry> <Label x:Name="helpLabel" Text="Enter Xamarin (case sensitive) in the entry above."> <VisualStateManager.VisualStateGroups> <VisualStateGroup Name="ValidityStates"> <VisualState Name="Valid"> <VisualState.Setters> <Setter Property="TextColor" Value="Transparent" /> </VisualState.Setters> </VisualState> <VisualState Name="Invalid" /> </VisualStateGroup> </VisualStateManager.VisualStateGroups> </Label> <Button x:Name="submitButton" Text="Submit" IsEnabled="false" FontSize="Large" Margin="0, 20" VerticalOptions="Center" HorizontalOptions="Center"> <VisualStateManager.VisualStateGroups> <VisualStateGroup Name="ValidityStates"> <VisualState Name="Valid"> <VisualState.Setters> <Setter Property="IsEnabled" Value="True" /> </VisualState.Setters> </VisualState> <VisualState Name="Invalid"> <VisualState.Setters> <Setter Property="IsEnabled" Value="False" /> </VisualState.Setters> </VisualState> </VisualStateGroup> </VisualStateManager.VisualStateGroups> </Button>

When the user enters “Xamarin” in the Entry, the second DataChangedBehavior is executed. This behaviour listens for the bound data to meet a specified condition, in this case that the bound data is equal to “Xamarin”, and executes the GoToStateAction instances in response. These transition the TargetObjects (helpLabel and submitButton) to the StateName (Valid), which causes the Label to disappear (its TextColor is set to Transparent) and the Button to be enabled.

Similarly, the first DataChangedBehavior is executed when the bound data (the Text property of the Entry) is not equal to “Xamarin”. Therefore, if the user deletes “Xamarin” (after entering it) the Invalid states of helpLabel and submitButton are transitioned to, which causes the Label to appear and the Button to be disabled. Note that this is a contrived example, and it would be unperformant to repeatedly transition to a VisualState on every key press in an input control.

Summary

The GoToStateAction class represents an action that will transition a VisualElement to a specified VisualState when triggered. It turns the specified TargetObject to the VisualState of StateName when triggered. The advantage of using the GoToStateAction class is that visual states can be transitioned to from XAML, without requiring use of the VisualStateManager class from code-behind.

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

For more information about visual states, see The Xamarin.Forms Visual State Manager.