Créer un contrôle réutilisable 100% Xamarin.Forms, partie 1
Tags: xamarinforms, xaml, fromscratch, french
Published: 2020-10-22 15:30:59

---
title: Créer un contrôle réutilisable 100% Xamarin.Forms, partie 1
published: true
date: 2020-10-22 10:13:26 UTC
tags: XamarinForms,XAML,Fromscratch,french
cover_image: https://dev-to-uploads.s3.amazonaws.com/i/a4zedzp4qvr4o9dv2n0s.jpg
canonical_url: https://www.sylvainmoingeon.fr/creer-un-controle-reutilisable-100-xamarinforms-partie-1/
---
Dans cet article "[from scratch](https://www.sylvainmoingeon.fr/tags/from-scratch/)" en deux parties, je vous présente une manière simple de créer une image circulaire avancée et réutilisable à partir de zéro, sans custom renderer, ni bibliothèque tierce.

[Partie 1](https://www.sylvainmoingeon.fr/creer-un-controle-reutilisable-100-xamarinforms-partie-1/) :

- Créer une image circulaire à l'aide de la propriété `Clip` et des `Geometry` récemment apparues dans [Xamarin.Forms](https://www.sylvainmoingeon.fr/tags/xamarinforms/)
- Afficher un indicateur d'activité pendant le chargement de l'image  
- Afficher une image de substitution en cas d'absence d'image ou d'url invalide en utilisant la technique de superposition dans une `Grid`  
- Afficher une bordure autour de l'image à l'aide des `Shape`, autre nouveauté de [Xamarin.Forms](https://www.sylvainmoingeon.fr/tags/xamarinforms/)  
- Utiliser des [fonctionnalités expérimentales](https://docs.microsoft.com/en-us/xamarin/xamarin-forms/internals/experimental-flags) avec la version stable actuelle de [Xamarin.Forms](https://www.sylvainmoingeon.fr/tags/xamarinforms/)  

[Partie 2](#non-existing-post-with-id-37) :

- Créer un contrôle réutilisable et compatible [MVVM](https://www.sylvainmoingeon.fr/tags/mvvm/)

  - Définir des propriétés bindables (`BindableProperty`)
  - Utiliser les `Converters`

Les présentations sont faites, place au concret.

* * *

> Certaines fonctionnalités comme les [Shapes](https://docs.microsoft.com/en-us/xamarin/xamarin-forms/user-interface/shapes/) et leurs [Geometries](https://docs.microsoft.com/en-us/xamarin/xamarin-forms/user-interface/shapes/geometries) nécessitent Xamarin.Forms 4.8 ou supérieure, pensez bien à mettre-à-jour le package après avoir créé votre solution !

## Commençons par la fin 

Pas de suspens, je vous livre d'emblée un visuel pour que vous sachiez où je vous amène. Celui-ci simule une page de contact :

[![Démonstration de l'image circulaire avec ses différentes options](https://www.sylvainmoingeon.fr/media/posts/36/demo-circleimage-3.gif)](https://github.com/SylvainMoingeon/CircleImageDemo)<figcaption>Démonstration de l'image circulaire avec ses différentes options</figcaption>

Niveau contenu, la page affiche une liste de contacts dont chaque photo provient d'une source différente :

1. Une photo chargée à partir d'une url
2. Une photo ajoutée au projet en tant que ressource
3. Pas de photo
4. Une photo pointant sur une url retournant une erreur 404
5. Une photo pointant sur une url inaccessible

Les trois derniers cas présentent une image de substitution par défaut car aucune photo valide ne peut être affichée.

Les contacts enregistrés comme favoris sont signalés par une bordure jaune autour de la photo.

Pour finir, si vous observez bien l'animation vous remarquerez un bref indicateur de chargement sur les deux dernières photos, le temps que Xamarin.Forms échoue à charger les images.

Mais je vous vois frétiller d'impatience, vous êtes venu ici pour voir du code, alors allons-y

## Clip clip clip, découpage de l'image 

Plutôt que de vous livrer de gros blocs de code illisibles sur une page web, je vais dans cet article aller directement à l'essentiel, vous trouverez le code complet sur mon [compte GitHub](https://github.com/SylvainMoingeon), comme d'habitude.

Pour commencer, comment afficher une image circulaire ?

Nous allons utiliser deux fonctionnalités apparues dernièrement dans Xamarin.Forms, la propriété [Clip](https://docs.microsoft.com/en-us/xamarin/xamarin-forms/user-interface/shapes/geometries#clip-with-a-geometry) (_découper_ en anglais) et les [Geometries](https://docs.microsoft.com/en-us/xamarin/xamarin-forms/user-interface/shapes/geometries).

`Clip` est une propriété des [VisualElements](https://docs.microsoft.com/en-us/dotnet/api/xamarin.forms.visualelement), de type `Geometry` et qui définit le contour du contenu d'un élément visuel. 

Ce n'est pas clair ? Un exemple, celui qui nous concerne : j'ai une image carrée et je souhaite afficher uniquement son contenu inscrit dans un cercle. Je vais donc définir une `Geometry` circulaire et l'appliquer à `Clip` pour découper l'image selon cette forme.

Attention les yeux, voici le code :

```xml
<Image Source="monimage.png" width="200" Height="200">
    <Image.Clip>
        <EllipseGeometry RadiusX="100"
                         RadiusY="100"
                         Center="100,100" />
    </Image.Clip>
</Image>
```

La géométrie circulaire n'existant pas, nous nous baserons sur une ellipse. Celle-ci se définit à partir d'un centre et de deux rayons, horizontal (`RadiusX`) et vertical (`RadiusY`). Pour obtenir un cercle, il suffit que `RadiusX` et `RadiusY` aient la même valeur. `Center` permet de positionner l'ellipse par rapport à son contenant.

Pour simplifier le calcul et le positionnement, autant partir d'une image carrée.

Pour obtenir un cercle centré et inscrit dans le carré, les rayons seront moitié plus petits que le côté de l'image et le centre du cercle sera au centre de l'image.

Voilà, obtenir une image circulaire c'est devenu aussi simple que ça avec Xamarin.Forms. Beaucoup de texte et d'explications pour quelque chose devenu finalement assez trivial !

## L'image de substitution 

Comment faire pour afficher une image par défaut quand il n'y a pas d'image à afficher ?

On va utiliser une technique ancienne et bien connue mais toujours très utile : l'empilement dans une `Grid`. Les éléments contenus dans une même cellule d'une Grid se superposent les uns sur les autres, le dernier déclaré dans le fichier Xaml étant au-dessus, la lecture de celui-ci étant séquentielle.

Nous allons donc définir une Grid à une seule cellule et y ajouter deux `Images` : la première est destinée à afficher l'image de substitution, la seconde à afficher l'image proprement dite.

Pour que les deux images se superposent sans décalage, il est nécessaire que les deux composants `Image` et leurs ellipses aient les mêmes dimensions.

Nous renseignerons également les propriétés `HorizontalOptions` et `VerticalOptions` de la Grid avec des valeurs qui forceront celle-ci à adopter les mêmes dimensions que son contenu.

> [DRY](https://fr.wikipedia.org/wiki/Ne_vous_r%C3%A9p%C3%A9tez_pas) - Je vais avoir besoin de créer deux fois une image circulaire simple, et il est probable que ce besoin se répète dans d'autres contextes. J'ai donc créé un contrôle `CircleImage` simple encapsulant le code précédent. Nous verrons dans la seconde partie de l'article comment créer un contrôle réutilisable.

```xml
<Grid HorizontalOptions="Center" VerticalOptions="Center">
    <local:CircleImage Source="monPlaceholder.png" ImageSize="128" />
    <local:CircleImage Source="monImage.png" ImageSize="128" />
</Grid>
```

De cette manière, l'image vient cacher le placeholder si elle est disponible, sinon on voit ce dernier à la place.

## L'indicateur de chargement 

Aujourd'hui c'est atelier empilage, nous allons encore superposer : ajoutons un `ActivityIndicator` à notre Grid.

L'`ActivityIndicator` a un visuel légèrement différent sous Android et iOS mais le principe reste le même : c'est un bidule qui tourne de façon hypnotique pour faire oublier le temps qui passe à l'utilisateur. Pour le voir tourner, il suffit d'affecter `True` à sa propriété `IsRunning`.

```xml
<Grid>
    <local:CircleImage x:Name="monPlaceholder" ImageSize="128" />
    <local:CircleImage x:Name="monImage" ImageSize="128" />
    
    <ActivityIndicator IsRunning="True"
                        VerticalOptions="Center"
                        HorizontalOptions="Center"
                        />
</Grid>
```

Nous avons donc un indicateur qui tourne au centre de notre image. C'est bien, mais il tourne en permanence et nous aurions besoin de l'activer uniquement pendant le chargement de l'image.

Cela tombe bien, le contrôle `Image` expose une propriété `IsLoading` qui vaut `True` pendant le chargement de l'image et `False` une fois le chargement terminé, peu importe que l'opération ce soit bien déroulée ou non.

Reste une question : comment lier la propriété `IsRunning` de l'indicateur à la propriété `IsLoading` de l'image ? Par un `Binding` bien entendu ! Mais comment binder une propriété d'un contrôle à une propriété d'un autre contrôle ? En précisant la source du Binding !

Le plus simple ici est de donner un nom explicite à notre contrôle Image (_monImage_ par exemple) et de se référer à ce nom comme source du Binding. La propriété visée sera définie comme `Path` du Binding : `"{Binding Source={x:Reference monImage}, Path=IsLoading}"`

Avec un peu plus de contexte, cela donne :

```xml
<Grid>
    <local:CircleImage x:Name="monPlaceholder" ImageSize="128" />
    <local:CircleImage x:Name="monImage" ImageSize="128" />
    
    <ActivityIndicator IsRunning="{Binding Source={x:Reference monImage}, Path=IsLoading}"
                        VerticalOptions="Center"
                        HorizontalOptions="Center"
                        />
</Grid>

```

De cette façon, nous verrons un indicateur de chargement tournoyer au centre de l'image durant le chargement de celle-ci.

![ActivityIndicator au centre d'une image](https://www.sylvainmoingeon.fr/media/posts/36/activityIndicator-3.gif)<figcaption>Et pourtant, elle tourne !</figcaption>

## La bordure 

Pour afficher une bordure autour de l'image, nous allons tirer parti des [`Shapes`](https://docs.microsoft.com/en-us/xamarin/xamarin-forms/user-interface/shapes/) encore sous forme expérimentale sous Xamarin.Forms 4.8.

Si la version de Xamarin.Forms que vous utilisez n'intègre pas encore les `Shapes` de façon officielle, vous devrez déclarer la fonctionnalité expérimentale dans le constructeur de la classe `App.xaml.cs` sous peine de lever une `InvalidOperationException` au lancement de l'application :

```csharp
public App()
{
    Device.SetFlags(new string[] { "Shapes_Experimental" });
    
    InitializeComponent();
    
    MainPage = new MainPage();
}

```

Les `Shapes` comme leur nom l'indique sont des formes que l'on peut ajouter en tant que `VisualElement` sur la page. La forme qui nous intéresse est là encore une `Ellipse`.

> Attention à ne pas confondre l'`EllipseGeometry` qui est la **description** d'un objet géométrique, avec l'`Ellipse` qui est un contrôle Visuel !

`Stroke` et `StrokeThickness` sont les deux propriétés exposées par l'Ellipse qui définissent la couleur et l'épaisseur de son contour. Nous n'aurons pas besoin des autres propriétés, mais nous pourrions affiner en définissant le type de contour (pointillé...) ou la couleur de remplissage de l'ellipse.

```xml
<Ellipse Stroke="Yellow" StrokeThickness="2" />
```

Ensuite, c'est juste une histoire d'empilage dans la `Grid`, vous commencez à avoir l'habitude.

```xml
<Grid>
    <local:CircleImage x:Name="monPlaceholder" ImageSize="128" />
    <local:CircleImage x:Name="monImage" ImageSize="128" />
    
    <Ellipse Margin="0"
             HorizontalOptions="Center"
             VerticalOptions="Center"
             Stroke="Yellow"
             StrokeThickness="2"
             HeightRequest="128"
             WidthRequest="128"
             />
             
    <ActivityIndicator [...] />
</Grid>
```

Ce qui donne :

![Une image circulaire bordée de jaune](https://www.sylvainmoingeon.fr/media/posts/36/circle-with-border.png)<figcaption>Une image circulaire bordée de jaune</figcaption>

## Fin de (première) partie 

L'article s'avère bien plus détaillé que je ne l'avais prévu au départ et ça commence à être bien consistant. Je vous laisse donc digérer tout ça et vous donne rendez-vous pour la seconde partie dans laquelle nous verrons comment assembler tout ceci pour en faire un contrôle réutilisable avec des propriétés Bindable pour le rendre "[MVVM](https://www.sylvainmoingeon.fr/tags/mvvm/) friendly".