Statistiques
| Branche: | Révision:

tuto_bindablelayout / Wiki.md @ a08e96d3

Historique | Voir | Annoter | Télécharger (11,355 ko)

1
---
2
title: "Tuto BindableLayout - Wiki"
3
---
4

    
5
## Objectif
6

    
7
L’objectif de ce tutoriel est de se familiariser avec l’élément BindableLayout pour afficher, avec beaucoup de liberté, les éléments d’une liste. 
8
Au cours de ce tutoriel nous allons créer l’interface suivante:
9
!Resultat_700.png!
10

    
11
Nous allons travailler avec le modèle Animal suivant :
12

    
13
```csharp
14
// Le modèle Animal avec ses 3 propriétés et accesseurs
15
public class Animal
16
{
17
    public int AnimalID { get; set; }
18
    public string Elevage { get; set; }
19
    public DateTime DateTraitement { get; set; }
20
}
21
```
22

    
23
La première page affiche cette liste d’animaux avec l’id et l’élevage, et lors du _tap_ sur un des animaux, une nouvelle page comportant toutes les informations est affichée.
24

    
25
## Création du projet
26

    
27
```json
28
.
29
├── BindableLayoutSample
30
│   ├── App.xaml
31
│   ├── App.xaml.cs
32
│   ├── AssemblyInfo.cs
33
│   ├── BindableLayoutSample.csproj
34
│   ├── DAO
35
│   │   └── AnimalDAO.cs
36
│   ├── Model
37
│   │   └── Animal.cs
38
│   ├── View
39
│   │   ├── AnimalDetailsView.xaml
40
│   │   │   └── AnimalDetailsView.xaml.cs
41
│   │   └── AnimalListView.xaml
42
│   │       └── AnimalListView.xaml.cs
43
│   └── ViewModel
44
│       ├── AnimalDetailsViewModel.cs
45
│       ├── AnimalItemViewModel.cs
46
│       ├── AnimalListViewModel.cs
47
│       └── Base
48
│           └── ViewModelBase.cs
49
└── BindableLayoutSample.Android
50
    └── ...
51
```
52

    
53
La première étape est de créer une application vide Xamarin.Forms.
54

    
55
- Supprimer les fichiers `Main.xaml` et `Main.xaml.cs`, c'est une page créée par défaut dont on ne se servira pas
56
- Créer les répertoires DAO, Model, View, ViewModel et le sous-répertoire Base dans ViewModel
57
- Implémenter le modèle `Animal` (présenté plus haut)
58

    
59
- Créer l’objet `AnimalDAO` (`AnimalDAO.cs`).
60

    
61
```csharp
62
// La classe d'accès aux données sur les animaux
63
public class AnimalDAO
64
{
65
    public async Task<List<Animal>> GetAllAnimals()
66
    {
67
        // Génération de "fausses" données
68
        List<Animal> ret = new List<Animal>()
69
        {
70
            new Animal()
71
            {
72
                AnimalID = 27852,
73
                DateTraitement = DateTime.Parse("2022-08-05 14:26"),
74
                Elevage = "PEIMA"
75
            },
76
            new Animal()
77
            {
78
                AnimalID = 2421,
79
                DateTraitement = DateTime.Parse("2022-08-05 12:33"),
80
                Elevage = "PEIMA"
81
            },
82
            new Animal()
83
            {
84
                AnimalID = 13569,
85
                DateTraitement = DateTime.Parse("2022-08-06 08:14"),
86
                Elevage = "Lees-Athas"
87
            },
88
            new Animal()
89
            {
90
                AnimalID = 45555,
91
                DateTraitement = DateTime.Parse("2022-08-07 16:21"),
92
                Elevage = "Donzacq"
93
            },
94
            new Animal()
95
            {
96
                AnimalID = 8956,
97
                DateTraitement = DateTime.Parse("2022-08-07 18:01"),
98
                Elevage = "PEIMA"
99
            },
100
        };
101

    
102
        // Simulation d'attente d'accès aux données (WS, BDD, ...)
103
        await Task.Delay(1000);
104
        return ret;
105
    }
106
}
107
```
108

    
109
- Créer les _Views_ et _ViewModels_ en ajoutant public avant chaque déclaration mais en les laissant vides (pour l’instant…)
110
- Faire le binding pour chaque paire _View - ViewModel_:
111

    
112
Dans `AnimalDetailsView.xaml.cs`:
113

    
114
```csharp
115
public AnimalDetailsView()
116
{
117
    InitializeComponent();
118
    BindingContext = new AnimalDetailsViewModel();
119
}
120
```
121

    
122
Dans `AnimalListView.xaml.cs`:
123

    
124
```csharp
125
public AnimalListView()
126
{
127
    InitializeComponent();
128
    BindingContext = new AnimalListViewModel();
129
}
130
```
131

    
132
A noter que nous avons un 3ème _ViewModel_, `AnimalViewModel` qui n’a pas de _View_ liée :  c’est normal, on s’en occupera plus tard.
133

    
134
- Implémenter le `ViewModelBase` qui implémente `INPC` (on utilisera la fonction `SetProperty` lors d'assignation de valeur sur nos propriétés)
135

    
136
```csharp
137
public class ViewModelBase : INotifyPropertyChanged
138
{
139
    // Propriété de notification de changement
140
    public event PropertyChangedEventHandler PropertyChanged;
141

    
142
    // Procédure de changement de valeur
143
    // Déclenche la fonction de notification
144
    protected bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyname = null)
145
    {
146
        if (Equals(storage, value))
147
        {
148
            return false;
149
        }
150

    
151
        storage = value;
152
        OnPropertyChanged(propertyname);
153
        return true;
154
    }
155

    
156
    // Fonction de notification 
157
    // Déclenche l'évènement de changement de valeur
158
    // Avec en argument le nom de la propriété qui a changé
159
    protected void OnPropertyChanged(string propertyName)
160
    {
161
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
162
    }
163
}
164
```
165

    
166
- Et finalement : définir la propriété `MainPage` dans `App.xaml.cs` sur `AnimalListView`. En le mettant dans une `NavigationPage`, on active la navigation hiercharchique (possibilité de revenir en arrière quand on navigue vers une nouvelle page).
167

    
168
```csharp
169
public App()
170
{
171
    InitializeComponent();
172

    
173
    // Point d'entrée de l'application
174
    MainPage = new NavigationPage(new AnimalListView());
175
}
176
```
177

    
178
## La page d’accueil et chargement des données
179

    
180
### Création de la liste
181

    
182
Dans le fichier `AnimalListView.xaml`, nous allons itérer sur une liste d’animaux que l'on nommera `AnimalList`. Cette liste contient les données que l’on souhaite afficher. On devra donc définir comment ces informations doivent être affichées. De plus, sur chaque _tap_ on veut naviguer vers une page de détail de l’animal concerné. 
183

    
184
Tout d'abord, on souhaite itérer sur `AnimalList` dans le fichier XAML. Chaque objet de liste comportera des éléments auxquels on pourra se lier à l’aide d’un _Binding_. En utilisant un _Binding_, on doit utiliser un nouveau _ViewModel_ pour chaque objet de la liste (les Views ne peuvent être bindées qu’à des _ViewModels_, c’est la règle MVVM!).
185

    
186
On aura donc plusieurs _ViewModel_ liés à la même page… et c’est valide ! La règle étant qu’un _ViewModel_ **ne doit être lié qu’à une seule page**, rien ne nous empêche d’avoir plusieurs _ViewModel_ par page, et c’est même la manière de faire pour notre problématique.
187

    
188
Pour pouvoir utiliser le _Binding_ et ajouter la fonctionnalité de navigation sans toucher au modèle animal, nous allons créer un nouveau _ViewModel_ : `AnimalItemViewModel`. Ce _ViewModel_ comportera les informations de l’animal ainsi que la commande de navigation vers la page de détail de cet animal.
189

    
190
Le code de `AnimalItemViewModel.cs` donne :
191

    
192
```csharp
193
public class AnimalItemViewModel : ViewModelBase
194
{
195
    private Animal animal;
196

    
197
    // Les infos de l'animal
198
    public Animal Animal
199
    {
200
        get => animal;
201
        set => SetProperty(ref animal, value);
202
    }
203

    
204
    // La commande de navigation est asynchrone,
205
    // Pour ne pas bloquer le Thread principal en charge de l'affichage
206
    // pendant l'instanciation de la nouvelle Page
207
    private async Task NavigateToAnimalDetails()
208
    {
209
        // Petite attente pour rendre la transition plus fluide
210
        await Task.Delay(150);
211

    
212
        // L'utilisation de la navigation page nous permet d'avoir une navigation verticale (push & pop)
213
        await Application.Current.MainPage.Navigation.PushAsync(new AnimalDetailsView(Animal));
214
    }
215

    
216
    public ICommand NavigateToAnimalCommand { private set; get; }
217

    
218
    public AnimalItemViewModel(Animal _animal)
219
    {
220
        Animal = _animal;
221
        NavigateToAnimalCommand = new Command(async () => await NavigateToAnimalDetails());
222
    }
223

    
224
}
225
```
226

    
227
On instancie ensuite une liste observable (`ObservableCollection`) de ce modèle. Cette liste est ensuite alimentée avec les données de chaque animal chargé.
228

    
229
Dans `AnimalListViewModel.cs`, déclaration de l'`ObservableCollection`:
230

    
231
```csharp
232
// La liste d'animaux qui sera alimentée avec les données récupérées par la DAO
233
// L'objet ObservableCollection implémente directement INPC sur la fonction Add
234
public ObservableCollection<AnimalItemViewModel> AnimalList { get; set; }
235
```
236

    
237
Alimentation de la liste (à mettre dans le constructeur ou dans une commande):
238

    
239
```csharp
240
// On alimente la liste observée par la view (AnimalList)
241
foreach (Animal _animal in result)
242
{
243
    AnimalItemViewModel toAddItem = new AnimalItemViewModel(_animal);
244
    AnimalList.Add(toAddItem);
245
}
246
```
247

    
248
#### Et comment ça se passe du côté de l’affichage (le XAML) ?
249

    
250
On veut créer une liste dans le XAML liée à notre `AnimalList` et en même temps gérer l’affichage en itérant chaque élément de la liste. Pour cela on utilise la propriété `BindableLayout` de la balise `StackLayout`.
251

    
252
En liant la propriété `BindableLayout.ItemSource` à une liste observable (d’où `ObservableCollection`), on peut itérer sur notre liste dans le XAML et gérer l’affichage pour chaque itération. 
253

    
254
Grâce à l'enchaînement de balises suivantes on peut personnaliser l’affichage de chaque élément:
255

    
256
```xml
257
<StackLayout Padding="0,5" BindableLayout.ItemsSource="{Binding AnimalList}">
258
    <!--
259
        On entre dans une itération sur BindableLayout.ItemSource donc AnimalList
260
    -->
261
    <BindableLayout.ItemTemplate>
262
        <!--
263
            Dans le dataTemplate, le BindingContext est lié à l'AnimalItemViewModel
264
            de l'itération
265
        -->
266
        <DataTemplate>
267
            <!--
268
                Lors du "tap", on utilise la commande de navigation définie
269
                dans AnimalItemViewModel
270
            -->
271
            <Grid
272
                Padding="5"
273
                xe:Commands.Tap="{Binding NavigateToAnimalCommand}"
274
                xe:TouchEffect.Color="#4b6da6"
275
                ColumnDefinitions="*,2*"
276
                RowDefinitions="Auto, Auto"
277
                RowSpacing="0">
278
                <!--
279
                    Les infos qu'on cherche se trouve dans l'objet Animal de
280
                    AnimalItemViewModel
281
                -->
282
                <Label
283
                    Grid.Row="0"
284
                    Grid.Column="0"
285
                    FontAttributes="Bold"
286
                    HorizontalOptions="CenterAndExpand"
287
                    Text="{Binding Animal.AnimalID}"
288
                    TextColor="DarkCyan"
289
                    VerticalOptions="CenterAndExpand" />
290
                <Label
291
                    Grid.Row="0"
292
                    Grid.Column="1"
293
                    FontSize="Medium"
294
                    HorizontalOptions="CenterAndExpand"
295
                    Text="{Binding Animal.Elevage}"
296
                    TextColor="DarkGray" />
297
            </Grid>
298
        </DataTemplate>
299
    </BindableLayout.ItemTemplate>
300
</StackLayout>
301
```
302

    
303
Dans la balise `DataTemplate`, le _BindingContext_ change, au lieu d’être lié à la page `AnimalListView`, **il s’attache à l’élément traité par l’itération sur** `BindableLayout.ItemSource` donc à chaque `AnimalItemViewModel`. Ce fonctionnement est représenté sur le diagramme ci-dessous :
304

    
305
Le reste de l'application pour obtenir les pages des captures d'écran ci-dessus est du développement Xamarin fondamental, décrit dans d'autres tutoriels et formations de ce wiki.
306
L'ensemble du code source est disponible sur ce dépôt Git : https://forge-dga.jouy.inra.fr/projects/xamarin/repository/tuto_bindablelayout
307
L'adresse du .git est : https://forge-dga.jouy.inra.fr/git/xamarin.tuto_bl.git