• laurent@lioncoding.com

Un Behavior pour convertir les événements des Controls Xamarin en Command


Certains événements dans Xamarin.Forms ne supportent pas le Data Binding; par exemple les événements tels que TexChanged, Focused, Pressed (Il en existe toute une liste).

Alors c’est quoi donc l’intérêt de faire supporter le Binding par ces événements ?

Lorsque nous sommes dans le contexte MVVM dans Xamarin.Forms, il est important de faire les traitements (comme par exemple l’enregistrement d’un produit dans une Base de Données) dans un ViewModel et non dans le Code behind de la vue(du formulaire d’enregistrement de produit); ceci par le biais d’un d’un concept appelé Command, qui d’abord est un objet supportant Binding et qui permet de faire un traitement lorsque l’utilisateur effectue une action sur la vue (Clic sur le bouton d’enregistrement par exemple).

Comme les événements TexChanged, Focused, Pressed n’implémentent pas le Binding par défaut, nous sommes obliger de faire nos traitements dans le Code behind, ce qui n’est pas l’objectif du MVVM.

Pour résoudre ce problème, il est impératif de passer par le concept de Behavior qui nous permet d’apporter des comportement nouveaux aux objets visuels. Ici, c’est de pouvoir permettre le support du Binding par les méthodes(Textchanged, Focused, Pressed) de ces objets visuels.

Création du projet

Structure du projet

Implémentation d’un Behavior

La procédure pour implémenter un Behavior est la suivante:

  1. Héritez de la classe Behavior <T>, où T est le type de contrôle auquel le Behavior doit s’appliquer. Cette classe propose deux méthodes OnAttachedTo et OnDetachingFrom.
  2. La méthode OnAttachedTo est appelée lorsque l’événement se déclenche.
  3. Et la méthode OnDetachingFrom pour effectuer tout nettoyage d’actions effectuées.
  4. Implémentez la fonctionnalité principale du Behavior.

Notre Behavior de base est ceci :

using System;
using Xamarin.Forms;

namespace EventToCommand.Behaviors
{
    public class BehaviorBase<T> : Behavior<T> where T : BindableObject
    {
        public T AssociatedObject { get; private set; }

        protected override void OnAttachedTo(T bindable)
        {
            base.OnAttachedTo(bindable);
            AssociatedObject = bindable;

            if (bindable.BindingContext != null)
            {
                BindingContext = bindable.BindingContext;
            }

            bindable.BindingContextChanged += OnBindingContextChanged;
        }

        protected override void OnDetachingFrom(T bindable)
        {
            base.OnDetachingFrom(bindable);
            bindable.BindingContextChanged -= OnBindingContextChanged;
            AssociatedObject = null;
        }

        void OnBindingContextChanged(object sender, EventArgs e)
        {
            OnBindingContextChanged();
        }

        protected override void OnBindingContextChanged()
        {
            base.OnBindingContextChanged();
            BindingContext = AssociatedObject.BindingContext;
        }
    }
}

Invocation d’une Command en réponse à tout événement

Ici, nous aurons à créer un classe qui héritera de notre BehaviorBase<T>, le paramètre T sera de type View, donc tout Control. C’est à ce niveau qu’intervient la notion de Command.

Le code de notre classe:

using System;
using System.Reflection;
using System.Windows.Input;
using Xamarin.Forms;

namespace EventToCommand.Behaviors
{
    public class EventToCommandBehavior: BehaviorBase<View>
    {
        Delegate eventHandler;

        public static readonly BindableProperty EventNameProperty = BindableProperty.Create("EventName", typeof(string), typeof(EventToCommandBehavior), null, propertyChanged: OnEventNameChanged);
        public static readonly BindableProperty CommandProperty = BindableProperty.Create("Command", typeof(ICommand), typeof(EventToCommandBehavior), null);
        public static readonly BindableProperty CommandParameterProperty = BindableProperty.Create("CommandParameter", typeof(object), typeof(EventToCommandBehavior), null);
        public static readonly BindableProperty InputConverterProperty = BindableProperty.Create("Converter", typeof(IValueConverter), typeof(EventToCommandBehavior), null);

        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 Converter
        {
            get { return (IValueConverter)GetValue(InputConverterProperty); }
            set { SetValue(InputConverterProperty, value); }
        }

        protected override void OnAttachedTo(View bindable)
        {
            base.OnAttachedTo(bindable);
            RegisterEvent(EventName);
        }

        protected override void OnDetachingFrom(View bindable)
        {
            base.OnDetachingFrom(bindable);
            DeregisterEvent(EventName);
        }

        void RegisterEvent(string name)
        {
            if (string.IsNullOrWhiteSpace(name))
            {
                return;
            }

            EventInfo eventInfo = AssociatedObject.GetType().GetRuntimeEvent(name);
            if (eventInfo == null)
            {
                throw new ArgumentException(string.Format("EventToCommandBehavior: Can&apos;t register the &apos;{0}&apos; event.", EventName));
            }
            MethodInfo methodInfo = typeof(EventToCommandBehavior).GetTypeInfo().GetDeclaredMethod("OnEvent");
            eventHandler = methodInfo.CreateDelegate(eventInfo.EventHandlerType, this);
            eventInfo.AddEventHandler(AssociatedObject, eventHandler);
        }

        void DeregisterEvent(string name)
        {
            if (string.IsNullOrWhiteSpace(name))
            {
                return;
            }

            if (eventHandler == null)
            {
                return;
            }
            EventInfo eventInfo = AssociatedObject.GetType().GetRuntimeEvent(name);
            if (eventInfo == null)
            {
                throw new ArgumentException(string.Format("EventToCommandBehavior: Can&apos;t de-register the &apos;{0}&apos; event.", EventName));
            }
            eventInfo.RemoveEventHandler(AssociatedObject, eventHandler);
            eventHandler = null;
        }

        void OnEvent(object sender, object eventArgs)
        {
            if (Command == null)
            {
                return;
            }

            object resolvedParameter;
            if (CommandParameter != null)
            {
                resolvedParameter = CommandParameter;
            }
            else if (Converter != null)
            {
                resolvedParameter = Converter.Convert(eventArgs, typeof(object), null, null);
            }
            else
            {
                resolvedParameter = eventArgs;
            }

            if (Command.CanExecute(resolvedParameter))
            {
                Command.Execute(resolvedParameter);
            }
        }

        static void OnEventNameChanged(BindableObject bindable, object oldValue, object newValue)
        {
            var behavior = (EventToCommandBehavior)bindable;
            if (behavior.AssociatedObject == null)
            {
                return;
            }

            string oldEventName = (string)oldValue;
            string newEventName = (string)newValue;

            behavior.DeregisterEvent(oldEventName);
            behavior.RegisterEvent(newEventName);
        }
    }
}

Utilisation de notre Behavior

Un simple test de notre Behavior.

La vue

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage
    x:Class="EventToCommand.MainPage"
    xmlns="http://xamarin.com/schemas/2014/forms"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    xmlns:behaviors="clr-namespace:EventToCommand.Behaviors">

    <StackLayout
        HorizontalOptions="Center"
        Orientation="Horizontal"
        VerticalOptions="Center">
        <Entry Placeholder="Enter text" VerticalOptions="Center">
            <Entry.Behaviors>
                <behaviors:EventToCommandBehavior Command="{Binding EntryFocusedCommand}" EventName="Focused" />
                <behaviors:EventToCommandBehavior Command="{Binding EntryTextChangedCommand}" EventName="TextChanged" />
            </Entry.Behaviors>
        </Entry>
    </StackLayout>

</ContentPage>

ViewModel

using System.Windows.Input;
using Xamarin.Forms;

namespace EventToCommand.ViewModels
{
    public class MainViewModel : BaseViewModel
    {
        public ICommand EntryFocusedCommand { get; private set; }
        public ICommand EntryTextChangedCommand { get; private set; }

        public MainViewModel()
        {
            EntryFocusedCommand = new Command(FocusedAlert);
            EntryTextChangedCommand = new Command(TextChangedAlert);
        }


        private void FocusedAlert()
        {
            // Do action
        }

        private void TextChangedAlert()
        {
           // Do action
        }
    }
}

Résultat

Ressources

Voici le lien vers le code source de ce projet sur Github.


Commentaires