I love Converters in Xamarin Forms. They give you a nice, easy way to display something through your XAML based on a value in your binding data. And they are so easy to set up.
All converters inherit from IValueConverter and must implement two methods: Convert and ConvertBack.
| public class MyConverter : IValueConverter | |
| { | |
| public object Convert(object value, Type targetType, object parameter, CultureInfo culture) | |
| { | |
| // do something to get a new value | |
| } | |
| public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) | |
| { | |
| // and change it back if you want to | |
| } | |
| } |
Then, to use it, in your XAML, add your reference to the converter in either your App.xaml (if you expect to use it a lot) or directly on the page you wish to use it.
| <?xml version="1.0" encoding="utf-8" ?> | |
| <Application xmlns="http://xamarin.com/schemas/2014/forms" | |
| xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" | |
| xmlns:converter="clr-namespace:MyApp.Converter;assembly=MyApp" | |
| x:Class="MyApp.App"> | |
| <Application.Resources> | |
| <ResourceDictionary> | |
| <converter:ItemTappedEventArgsConverter x:Key="ItemTappedEventArgsConverter" /> | |
| <converter:FirstValidationErrorConverter x:Key="FirstValidationErrorConverter" /> | |
| <converter:BooleanNegationConverter x:Key="Inverter" /> | |
| </ResourceDictionary> | |
| </Application.Resources> | |
| </Application> |
I think the easiest way to see this in action is to run through some of the converters I’ve used. Most of these are probably common to a lot of Xamarin developers, but I’m going to add them in any way.
Boolean Negation Converter
This lets you convert a true to false, and false to true (obviously!)
| public class BooleanNegationConverter : IValueConverter | |
| { | |
| public object Convert(object value, Type targetType, object parameter, CultureInfo culture) | |
| { | |
| return !(bool)value; | |
| } | |
| public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) | |
| { | |
| return !(bool)value; | |
| } | |
| } |
First Validation Error Converter
This is a converter I came across while learning Xamarin Forms, from a Microsoft example project. It allows you to display the first error set against a bound value that is a ValidatableObject. I’d recommend looking at the eShopOnContainers sample code if you’re interested in learning more about this.
| public class FirstValidationErrorConverter : IValueConverter | |
| { | |
| public object Convert(object value, Type targetType, object parameter, CultureInfo culture) | |
| { | |
| ICollection<string> errors = value as ICollection<string>; | |
| return errors != null && errors.Count > 0 ? errors.ElementAt(0) : null; | |
| } | |
| public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) | |
| { | |
| throw new NotImplementedException(); | |
| } | |
| } |
Item Tapped Event Args Converter
This is another I came across when first learning Xamarin Forms and I still use it quite a bit.
| public class ItemTappedEventArgsConverter : IValueConverter | |
| { | |
| public object Convert(object value, Type targetType, object parameter, CultureInfo culture) | |
| { | |
| var eventArgs = value as ItemTappedEventArgs; | |
| if (eventArgs == null) | |
| throw new ArgumentException("Expected TappedEventArgs as value", "value"); | |
| return eventArgs.Item; | |
| } | |
| public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) | |
| { | |
| throw new NotImplementedException(); | |
| } | |
| } |
This allows you to capture an action against a control and bind it to a command. It needs to be used in conjunction with a Behavior, but it’s a very useful combo.
| public class EventToCommandBehavior : BindableBehavior<Xamarin.Forms.View> | |
| { | |
| public static BindableProperty CommandProperty = | |
| BindableProperty.CreateAttached( | |
| "Command", | |
| typeof(ICommand), | |
| typeof(EventToCommandBehavior), | |
| null, | |
| BindingMode.OneWay); | |
| public static BindableProperty CommandParameterProperty = | |
| BindableProperty.CreateAttached( | |
| "CommandParameter", | |
| typeof(object), | |
| typeof(EventToCommandBehavior), | |
| null, | |
| BindingMode.OneWay); | |
| public static BindableProperty EventArgsConverterProperty = | |
| BindableProperty.CreateAttached( | |
| "EventArgsConverter", | |
| typeof(IValueConverter), | |
| typeof(EventToCommandBehavior), | |
| null, | |
| BindingMode.OneWay); | |
| public static BindableProperty EventArgsConverterParameterProperty = | |
| BindableProperty.CreateAttached( | |
| "EventArgsConverterParameter", | |
| typeof(object), | |
| typeof(EventToCommandBehavior), | |
| null, | |
| BindingMode.OneWay); | |
| protected Delegate _handler; | |
| private static BindableProperty EventNameProperty = | |
| BindableProperty.CreateAttached( | |
| "EventName", | |
| typeof(string), | |
| typeof(EventToCommandBehavior), | |
| null, | |
| BindingMode.OneWay); | |
| private EventInfo _eventInfo; | |
| public string EventName | |
| { | |
| get { return (string)GetValue(EventNameProperty); } | |
| set { SetValue(EventNameProperty, value); } | |
| } | |
| public ICommand Command | |
| { | |
| get { return (ICommand)GetValue(CommandProperty); } | |
| set { SetValue(CommandProperty, value); } | |
| } | |
| public object CommandParameter | |
| { | |
| get { return GetValue(CommandParameterProperty); } | |
| set { SetValue(CommandParameterProperty, value); } | |
| } | |
| public IValueConverter EventArgsConverter | |
| { | |
| get { return (IValueConverter)GetValue(EventArgsConverterProperty); } | |
| set { SetValue(EventArgsConverterProperty, value); } | |
| } | |
| public object EventArgsConverterParameter | |
| { | |
| get { return GetValue(EventArgsConverterParameterProperty); } | |
| set { SetValue(EventArgsConverterParameterProperty, value); } | |
| } | |
| protected override void OnAttachedTo(Xamarin.Forms.View visualElement) | |
| { | |
| base.OnAttachedTo(visualElement); | |
| var events = AssociatedObject.GetType().GetRuntimeEvents().ToArray(); | |
| if (events.Any()) | |
| { | |
| this._eventInfo = events.FirstOrDefault(e => e.Name == EventName); | |
| if (this._eventInfo == null) | |
| throw new ArgumentException(String.Format("EventToCommand: Can't find any event named '{0}' on attached type", EventName)); | |
| AddEventHandler(this._eventInfo, this.AssociatedObject, this.OnFired); | |
| } | |
| } | |
| protected override void OnDetachingFrom(Xamarin.Forms.View view) | |
| { | |
| if (this._handler != null) | |
| { | |
| this._eventInfo.RemoveEventHandler(this.AssociatedObject, this._handler); | |
| } | |
| base.OnDetachingFrom(view); | |
| } | |
| private void AddEventHandler(EventInfo eventInfo, object item, Action<object, EventArgs> action) | |
| { | |
| var eventParameters = eventInfo.EventHandlerType | |
| .GetRuntimeMethods().First(m => m.Name == "Invoke") | |
| .GetParameters() | |
| .Select(p => Expression.Parameter(p.ParameterType)) | |
| .ToArray(); | |
| var actionInvoke = action.GetType() | |
| .GetRuntimeMethods().First(m => m.Name == "Invoke"); | |
| this._handler = Expression.Lambda( | |
| eventInfo.EventHandlerType, | |
| Expression.Call(Expression.Constant(action), actionInvoke, eventParameters[0], eventParameters[1]), | |
| eventParameters) | |
| .Compile(); | |
| eventInfo.AddEventHandler(item, this._handler); | |
| } | |
| private void OnFired(object sender, EventArgs eventArgs) | |
| { | |
| if (this.Command == null) | |
| { | |
| return; | |
| } | |
| var parameter = this.CommandParameter; | |
| if (eventArgs != null && eventArgs != EventArgs.Empty) | |
| { | |
| parameter = eventArgs; | |
| if (this.EventArgsConverter != null) | |
| { | |
| parameter = this.EventArgsConverter.Convert(eventArgs, typeof(object), this.EventArgsConverterParameter, CultureInfo.CurrentUICulture); | |
| } | |
| } | |
| if (this.Command.CanExecute(parameter)) | |
| { | |
| this.Command.Execute(parameter); | |
| } | |
| } | |
| } |
For example, I can bind the tapping of a ListView item to my code in my View Model by doing:-
| <ListView ItemsSource="{Binding ListData}" HasUnevenRows="True"> | |
| <ListView.Behaviors> | |
| <behaviors:EventToCommandBehavior EventName="ItemTapped" Command="{Binding ListItemSelected}" EventArgsConverter="{StaticResource ItemTappedEventArgsConverter}" /> | |
| </ListView.Behaviors> | |
| <ListView.ItemTemplate> | |
| <DataTemplate> | |
| <ViewCell> | |
| <!- your layout here–> | |
| </ViewCell> | |
| </DataTemplate> | |
| </ListView.ItemTemplate> | |
| </List> |
String Has Value Converter
This was one I used to show a message if one had been supplied when a busy indicator was being displayed.
| public class StringHasValueConverter : IValueConverter | |
| { | |
| public object Convert(object value, Type targetType, object parameter, CultureInfo culture) | |
| { | |
| if (value == null) return false; | |
| return !string.IsNullOrEmpty(value.ToString()); | |
| } | |
| public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) | |
| { | |
| throw new NotImplementedException(); | |
| } | |
| } |
| <?xml version="1.0" encoding="utf-8" ?> | |
| <ResourceDictionary | |
| x:Class="MyApp.Styles.Indicator" | |
| xmlns="http://xamarin.com/schemas/2014/forms" | |
| xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"> | |
| <ControlTemplate x:Key="MainTemplate"> | |
| <Grid BindingContext="{TemplateBinding BindingContext}"> | |
| <ContentPresenter Grid.Row="0" /> | |
| <!– 'Busy' indicator –> | |
| <StackLayout | |
| Grid.Row="0" | |
| BackgroundColor="{DynamicResource BackgroundIndicatorColor}" | |
| HorizontalOptions="FillAndExpand" | |
| IsVisible="{TemplateBinding BindingContext.IsBusy}" | |
| VerticalOptions="FillAndExpand"> | |
| <StackLayout | |
| HorizontalOptions="CenterAndExpand" | |
| VerticalOptions="CenterAndExpand"> | |
| <Frame | |
| Margin="10" | |
| Padding="10" | |
| CornerRadius="40" | |
| HeightRequest="100" | |
| WidthRequest="200"> | |
| <StackLayout> | |
| <ActivityIndicator | |
| BackgroundColor="Transparent" | |
| HeightRequest="48" | |
| HorizontalOptions="Center" | |
| IsRunning="{TemplateBinding BindingContext.IsBusy}" | |
| IsVisible="{TemplateBinding BindingContext.IsBusy}" | |
| VerticalOptions="Center" | |
| WidthRequest="48" | |
| Color="{DynamicResource IndicatorColor}" /> | |
| <Label | |
| IsVisible="{TemplateBinding BindingContext.IsBusyText, | |
| Converter={StaticResource StringHasValueConverter}}" | |
| HorizontalOptions="Center" | |
| HorizontalTextAlignment="Center" | |
| Style="{DynamicResource TextPrimaryColor}" | |
| Text="{TemplateBinding BindingContext.IsBusyText}" /> | |
| </StackLayout> | |
| </Frame> | |
| </StackLayout> | |
| </StackLayout> | |
| </Grid> | |
| </ControlTemplate> | |
| </ResourceDictionary> |
Alternate Row Colour Converter
Set alternating colours on a Listview.
| public class AlternateRowColourConverter : IValueConverter | |
| { | |
| public object Convert(object value, Type targetType, object parameter, CultureInfo culture) | |
| { | |
| if (value == null || parameter == null) return Color.LightGray; | |
| var index = ((ListView)parameter).ItemsSource.Cast<object>().ToList().IndexOf(value); | |
| if (index % 2 == 0) | |
| { | |
| return "#eaecef"; | |
| } | |
| else | |
| { | |
| return "#d7d9dd"; | |
| } | |
| } | |
| public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) | |
| { | |
| throw new NotImplementedException(); | |
| } | |
| } |
| <ListView> | |
| <ListView.ItemTemplate> | |
| <DataTemplate> | |
| <ViewCell> | |
| <Grid | |
| Padding="5" | |
| BackgroundColor="{Binding ., Converter={StaticResource AlternateRowColourConverter}, ConverterParameter={x:Reference ItemsToFit}}" | |
| HorizontalOptions="FillAndExpand"> | |
| <!– your stuff here –> | |
| </Grid> | |
| </ViewCell> | |
| </DataTemplate> | |
| </ListView.ItemTemplate> | |
| </ListView> |
Direction Icon Converter
This was used to display a direction icon, utilising the Material Design Icons as per James Montemagno’s post, making it really easy to show an icon against each row.
| public class DirectionIconConverter : IValueConverter | |
| { | |
| public object Convert(object value, Type targetType, object parameter, CultureInfo culture) | |
| { | |
| var direction = value.ToString().ToLower(); | |
| switch (direction) | |
| { | |
| case "north": | |
| return IconConstants.ArrowUpBoldCircle; | |
| case "south": | |
| return IconConstants.ArrowDownBoldCircle; | |
| case "east": | |
| return IconConstants.ArrowRightBoldCircle; | |
| case "west": | |
| return IconConstants.ArrowLeftBoldCircle; | |
| default: | |
| return IconConstants.HelpCircle; | |
| } | |
| } | |
| public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) | |
| { | |
| throw new NotImplementedException(); | |
| } | |
| } |

Conclusion
Hopefully, this has shown just how useful and easy converters are to use, as well as some ideas on how you could use them in your apps.