Synchroniser une TListView avec une table de base de données ou un ensemble d'enregistrements

J'ai hésité entre FireDAC et Firemonkey pour poster ce texte, mais je me dis que le plus important dans l'histoire touche plus Firemonkey et l'accès général aux bases de données donc voici ma réponse à une question que l'on m'a posée cette semaine : "Comment synchroniser une TListView avec une table de base de données et connaître les champs de l'enregistrement d'origine lorsqu'on clique sur une ligne ?".

Peu importe à quoi ressemble la TListView et l'apparence choisie pour ses éléments. Je vous donne deux possibilités mais il y en a d'autres.

Firemonkey ne comporte aucun composant dédié à saisir ou afficher des informations liées à une base de données. On doit donc soit passer par LiveBindings soit à la main pour faire les affichages et modifications de données.

Dans le cas du LiveBindings, si on remplit une liste à partir d'un ensemble de données, les deux restent synchronisés. Se promener sur l'un bouge l'autre. Sélectionner un élément de la TListView permet le positionnement de l'ensemble de données sur l'enregistrement correspondant. Il suffit ensuite de récupérer les informations dont on a besoin, par exemple comme ceci :

procedure TForm1.ListView1ItemClickEx(const Sender: TObject; ItemIndex: Integer; const LocalClickPos: TPointF; const ItemObject: TListItemDrawable);
begin
    Edit1.Text := EmployeesTable.FieldByName('Lastname').AsString;
end;

Vous pouvez consulter un programme d'exemple depuis ce lien.

L'autre solution nécessite à peine plus de programmation.

Le remplissage de la TListView ne se faisant pas avec LiveBindings c'est à nous de le faire.

Ici je propose de charger la table qui nous intéresse dès son ouverture.

procedure TForm1.EmployeesTableAfterOpen(DataSet: TDataSet);
var
  item: TListViewItem;
begin
  EmployeesTable.First;
  while not EmployeesTable.Eof do
  begin
    item := ListView1.Items.Add;
    item.Text := EmployeesTable.FieldByName('Lastname').AsString;
    item.Tag := EmployeesTable.FieldByName('EmployeeID').AsInteger;
    EmployeesTable.Next;
  end;
end;

On profite de l'existence de la propriété Tag (dans quasiment tous les composants) pour y stocker la clé de l'ensemble de données. On pourrait utiliser TagString, TagFloat ou TagObject selon ce qu'on veut embarquer avec chaque élément de la TListView.

Et lorsqu'on clique sur un élément de la liste, il suffit de se positionner sur l'ensemble de données et d'en récupérer les informations.

procedure TForm1.ListView1ItemClickEx(const Sender: TObject; ItemIndex: Integer; const LocalClickPos: TPointF; const ItemObject: TListItemDrawable);
begin
  if EmployeesTable.Locate('EmployeeID', ListView1.Items[ItemIndex].Tag) then
    Edit1.Text := EmployeesTable.FieldByName('Lastname').AsString;
end;

Ce second exemple est disponible sur cette page.

Sur tablette et smartphone il n'y a pas de grosse différence pour l'utilisateur puisqu'il faut cliquer sur un élément de liste pour le sélectionner. En revanche ce n'est pas le cas pour macOS et Windows puisqu'on peut se déplacer avec les flèches du clavier une fois que la TListView a eu le focus.

Dans le cas du LiveBindings la liste et l'ensemble de données sont synchronisés. Les modifications apparaissent donc bien en bougeant la sélection.

Ce n'est pas le cas dans le second exemple où on a choisi de n'afficher l'info que lors du clic. Il aurait été préférable de gérer la synchronisation dans l'événement TListView.OnChange (déclenché quand son ItemIndex est modifié) et de mettre seulement l'action à faire sur un clic dans le TListView.OnItemClick (si on ne change pas les éléments de la liste) ou TListView.OnItemClickEx (si on modifie les éléments de la liste).

Il y a également une autre différence : dans le cas du LiveBindings si un autre composant bouge le curseur de la table, il est répercuté automatiquement sur la liste. Dans le second cas c'est à nous de gérer ce changement d'enregistrement au niveau de l'ensemble de données pour le répercuter sur le TListView.ItemIndex.

N'hésitez pas à me contacter si vous avez des questions ou besoin d'éclaircissements.


Mug Pascal case in AlexandrieMug carte postale Sydney