Révision a08e96d3

Voir les différences:

BindableLayoutSample/BindableLayoutSample/Model/Animal.cs
8 8
        public int AnimalID { get; set; }
9 9
        public string Elevage { get; set; }
10 10
        public DateTime DateTraitement { get; set; }
11

  
11 12
    }
12 13
}
BindableLayoutSample/BindableLayoutSample/View/AnimalListView.xaml
93 93
                                                Les infos qu'on cherche se trouve dans l'objet Animal de
94 94
                                                AnimalItemViewModel
95 95
                                            -->
96
                                            <Label Text="Bonjour" />
96 97
                                            <Label
97 98
                                                Grid.Row="0"
98 99
                                                Grid.Column="0"
BindableLayoutSample/BindableLayoutSample/ViewModel/AnimalItemViewModel.cs
38 38
            NavigateToAnimalCommand = new Command(async () => await NavigateToAnimalDetails());
39 39
        }
40 40

  
41
        public override string ToString()
42
        {
43
            return "" + Animal.AnimalID;
44
        }
45

  
41 46
    }
42 47
}
BindableLayoutSample/BindableLayoutSample/ViewModel/AnimalListViewModel.cs
13 13
    public class AnimalListViewModel : ViewModelBase
14 14
    {
15 15
        #region Attributes
16

  
17 16
        private bool isBusy;
18 17
        private bool isLoadedAnimals;
19

  
20 18
        #endregion
21 19

  
22 20
        #region Properties
......
37 35
        // La liste d'animaux qui sera alimentée avec les données récupérées par la DAO
38 36
        // L'objet ObservableCollection implémente directement INPC
39 37
        public ObservableCollection<AnimalItemViewModel> AnimalList { get; set; }
40

  
41 38
        #endregion
42 39

  
43 40
        #region Commands
BindableLayoutSample/BindableLayoutSample/ViewModel/Base/ViewModelBase.cs
29 29
        {
30 30
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
31 31
        }
32

  
33
        public ViewModelBase()
34
        {
35

  
36
        }
37

  
38 32
    }
39 33
}
Wiki.md
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

Formats disponibles : Unified diff