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; 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;lt;ListView&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.