Dans cet article nous allons voir comment créer deux Behaviors pour la ListView :

– Un pour permettre d’associer une commande lorsque l’un des éléments de la liste est cliqué.

– Un second pour implémenter le scroll infini

Une petite différence avec WPF :

Les Behaviors de Xamarin Forms sont un peu différent de ceux WPF.

Ceux-ci n’ont pas de propriété **AssociatedObject. **La seconde grosse différence est que leur DataContext n’est pas initialisé comme une vue normale.

Pour résoudre ces petits problèmes nous allons définir une classe de base à nos behaviors qui va rajouter ces deux comportements.


public abstract class BaseBehavior : Behavior<ListView>
where T : BindableObject
{
   public T AssociatedObject { get; private set; }

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

      // Lors de la construction on définit la propriété AssociatedObject
      AssociatedObject = bindable;

      //Si le contexte est != NULL on initiliase le contexte de du Behavior avec celui de l'objet courant
      if (bindable.BindingContext != null)
      {
      BindingContext = bindable.BindingContext;
      }

      bindable.BindingContextChanged += OnBindingContextChanged;
   }

   protected override void OnDetachingFrom(T bindable)
   {
      base.OnDetachingFrom(bindable);
      //On se désabonne
      bindable.BindingContextChanged -= OnBindingContextChanged;

      // Lors de la construction on définit la propriété AssociatedObject
      AssociatedObject = null;
   }

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

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

OnItemTappedBehavior :

Dans mes projets je veux souvent associer une commande à l’action click/touch utilisateur sur une ligne de ma ListView. Or Xamarin Forms ne fournit pas ce mécanisme par défaut.

Je vais donc devoir créer un Behavior qui s’abonne à l’évènement ItemTapped et qui invoque ma commande.


    public class ListViewOnItemTappedBehavior : BaseBehavior<ListView>
    {
        protected override void OnAttachedTo(ListView bindable)
        {
            base.OnAttachedTo(bindable);
            // Abonnement à ItemTapped
            bindable.ItemTapped += Bindable_ItemTapped;
        }

        protected override void OnDetachingFrom(ListView bindable)
        {
            base.OnDetachingFrom(bindable);
            // Désabonnement à ItemTapped (très important sans ça vous risquez de créer des fuites mémoire).
            bindable.ItemTapped -= Bindable_ItemTapped;
        }

        private void Bindable_ItemTapped(object sender, ItemTappedEventArgs e)
        {
            var cmd = Command;

            if (cmd != null &amp;amp;amp;amp;amp;&amp;amp;amp;amp;amp; cmd.CanExecute(null))
            {
               cmd.Execute(e.Item);
            }
        }

        public static readonly BindableProperty CommandProperty =
            BindableProperty.CreateAttached(
                nameof(Command),
                typeof(ICommand),
                typeof(ListViewOnItemTappedBehavior),
                null);

        public ICommand Command
        {
            get { return (ICommand)GetValue(CommandProperty); }
            set { SetValue(CommandProperty, value); }
        }
    }

Puis pour l’utiliser c’est très simple :


       <ListView.Behaviors>
            <behavior:ListViewOnItemTappedBehavior 
                        Command="{Binding Path=ItemSelectedCommand, Mode=OneWay}" />
        </ListView.Behaviors>

InfiniteScrollBehavior:

Dans quasiment tout mes projets j’ai désormais besoin de liste qui s’auto pagine.

L’idée est que lorsque l’on arrive vers la fin de la liste on commence a chargé une nouvelle page de celle-ci.

Pour ce faire nous aurons besoin de deux propriétés :

– La propriété Command qui sera invoqué lorsque la liste aura besoin de charger plus d’éléments.

– La propriété NumberOfItemsBeforeLoadMore qui sera a partir de quel index de fin de liste nous commenceront a charger une nouvelle page de donnée.


    public class InfiniteScrollBehavior : BaseBehavior&amp;amp;amp;amp;lt;ListView&amp;amp;amp;amp;gt;
    {
        public static readonly BindableProperty CommandProperty =
            BindableProperty.Create(
                nameof(Command),
                typeof(ICommand),
                typeof(InfiniteScrollBehavior),
                null);

        public ICommand Command
        {
            get
            {
                return (ICommand)GetValue(CommandProperty);
            }
            set
            {
                SetValue(CommandProperty, value);
            }
        }

        public static readonly BindableProperty NumberOfItemsBeforeLoadMoreProperty =
            BindableProperty.Create(
                nameof(NumberOfItemsBeforeLoadMore),
                typeof(uint),
                typeof(InfiniteScrollBehavior),
                1U,
                BindingMode.OneWay);

        public uint NumberOfItemsBeforeLoadMore
        {
            get
            {
                return (uint)GetValue(NumberOfItemsBeforeLoadMoreProperty);
            }
            set
            {
                SetValue(NumberOfItemsBeforeLoadMoreProperty, value);
            }
        }

        protected override void OnAttachedTo(ListView bindable)
        {
            base.OnAttachedTo(bindable);
            bindable.ItemAppearing += InfiniteListView_ItemAppearing;
        }

        protected override void OnDetachingFrom(ListView bindable)
        {
            base.OnDetachingFrom(bindable);
            bindable.ItemAppearing -= InfiniteListView_ItemAppearing;
        }

        private void InfiniteListView_ItemAppearing(object sender, ItemVisibilityEventArgs e)
        {
            var items = AssociatedObject.ItemsSource as IList;
            if (Command != null &&
                items != null &&
                items.IndexOf(e.Item) >= items.Count - NumberOfItemsBeforeLoadMore)
            {
                if (Command.CanExecute(null))
                {
                    Command.Execute(null);
                }
            }
        }
    }

Comme le behavior précèdent l’utilisation est aussi très simple.

A noter que la propriété permet régler à partir de quel item


        <ListView.Behaviors>
            <behavior:ListViewOnItemTappedBehavior 
                        Command="{Binding Path=ItemSelectedCommand, Mode=OneWay}" />
            <behavior:InfiniteScrollBehavior 
                        NumberOfItemsBeforeLoadMore="10"
                        Command="{Binding Path=LoadMoreCommand, Mode=OneWay}" />
        </ListView.Behaviors>

Pour aller plus loin :

Je vous conseille de regarder l’excellent podcast Quoi de neuf sur Xamarin.Forms ? ou le format audio avec comme invité Thomas Lebrun.