IV. Exemple 2 : édition d'une table au choix▲
Notre premier exemple nous a permis de nous familiariser avec les composants nécessaires à la communication avec une base de données MySQL.
Nous allons créer un second projet un peu plus élaboré, qui va nous permettre de sélectionner une des tables, de l'afficher dans un DBGrid et d'en modifier le contenu.
Créez un nouveau projet de type Application.
Dans l'inspecteur d'objets, renommez la fiche principale (Form1 par défaut) en MainForm. Agrandissez la taille de la fiche. Déposez-y, tout comme dans le premier exemple, les composants invisibles qui permettent de communiquer avec la base de données :
- un connecteur de la bonne version, que vous renommez directement en MySQLConnection ;
- un TSQLTransaction ;
- un TSQLQuery.
Cette fois, les propriétés HostName, DatabaseName, UserName et Password de MySQLConnection restent vierges de toute valeur initiale ; la propriété Transaction doit être initialisée à SQLTransaction1 et la propriété CharSet doit être initialisée à UTF8. La propriété Database de SQLQuery1 doit être initialisée à MySQLConnection.
IV-A. Mot de passe de connexion▲
Nous allons faire en sorte que l'utilisateur du programme tape, au début du programme, le mot de passe permettant de se connecter à la base de données. Nous allons faire cela au moment où la fenêtre de l'application reçoit le focus.
Cliquez, dans l'inspecteur d'objets, sur la fiche elle-même, et allez dans l'onglet Événements. Cliquez sur les trois points en regard de l'événement OnActivate, ce qui aura pour effet de créer dans le code source la méthode événementielle FormActivate.
Voici le contenu de cette méthode :
procedure
TMainForm.FormActivate(Sender: TObject);
var
LPassword : String
;
begin
MySQLConnection.HostName := '192.168.0.1'
;
MySQLConnection.DatabaseName := 'location'
;
MySQLConnection.UserName := 'mysqldvp'
;
if
InputQuery('Connexion à la base de données'
, 'Tapez votre mot de passe :'
, True
, LPassword)
then
begin
MySQLConnection.Password := LPassword;
try
MySQLConnection.Connected := True
;
SQLTransaction1.Active := True
;
except
on
e: EDatabaseError do
begin
MessageDlg('Erreur de connexion à la base de données.'#10#13'Le mot de passe est peut-être incorrect ?'#10#10#13'Fin de programme.'
, mtError, [mbOk], 0
);
Close;
end
;
end
;
end
else
(* Pas de mot de passe : fin de programme *)
Close;
end
;
Ajoutez également l'unité db à la clause uses (pour EDatabaseError).
Détaillons tout cela. Pour commencer, nous initialisons les propriétés HostName, DatabaseName et UserName du connecteur. Le mot de passe est demandé au moyen d'un dialogue InputQuery. Si l'utilisateur annule l'entrée du mot de passe, le programme se ferme sans autre forme de procès ; s'il entre un mot de passe erroné, l'exception déclenchée par le connecteur est interceptée dans le bloc try...except, un message d'erreur est affiché et l'application est fermée.
Avant de réaliser nos premiers tests à ce stade, n'oublions pas de faire en sorte que la connexion à la base de données soit proprement coupée à la fermeture du programme. Comme dans le premier exemple, créez une méthode événementielle pour l'événement OnClose :
procedure
TMainForm.FormClose(Sender: TObject; var
CloseAction: TCloseAction);
begin
SQLQuery1.Close;
if
SQLTransaction1.Active
then
SQLTransaction1.Active := False
;
if
MySQLConnection.Connected
then
MySQLConnection.Connected := False
;
end
;
Enregistrez le projet, en l'appelant mysql02 et en l'enregistrant dans un nouveau répertoire du même nom.
Compilez le programme et exécutez-le depuis l'EDI Lazarus. Vous constatez qu'une fenêtre vierge apparaît et qu'immédiatement le dialogue InputQuery vous demande le mot de passe :
Si vous tapez le mot de passe correct (« passmysqldvp », rappelez-vous), la fenêtre de l'application (qui ne contient rien de visible pour l'instant) reste ouverte ; si vous cliquez sur Cancel, l'application se ferme et si vous tapez exprès un mot de passe erroné, un message d'erreur apparaît :
Tiens, mais ce n'est pas le message d'erreur que nous avons prévu dans la méthode FormActivate ? En effet, mais si vous cliquez sur le bouton Continuer, celui que nous avions prévu apparaît bien :
Pourquoi ? Tout simplement parce que le premier message d'erreur est renvoyé par le débogueur, qui lève le premier l'exception.
Plutôt que d'exécuter le programme depuis l'EDI, utilisez l'exécutable qui a été créé dans le répertoire mysql02.
Exécutez-le et faites l'expérience d'entrer un mot de passe erroné : c'est bien le message d'erreur que nous avons prévu qui s'affiche, puisque l'exécution du programme n'est plus encadrée par le débogueur.
IV-A-1. Affichage de l'erreur renvoyée par le système▲
Notre message d'erreur n'est pas très précis, car il apparaît identiquement si le mot de passe est erroné et si, par exemple, le serveur est inaccessible ou la base de données inexistante.
Pour pouvoir identifier plus précisément l'erreur qui s'est produite, il faut lever une exception descendante de EDatabaseError : ESQLDatabaseError. Dans les propriétés de ce type d'exception se trouvent un code et des messages propres à MySQL.
Une liste des erreurs possibles est consultable dans la référence de MySQL en ligne.
Voici une version plus élaborée de notre méthode de login :
procedure
TMainForm.FormActivate(Sender: TObject);
(* Lecture du mot de passe et connexion à la base de données *)
var
LPassword : String
;
begin
(* Données de la connexion *)
MySQLConnection.HostName := '192.168.0.1'
;
MySQLConnection.DatabaseName := 'location'
;
MySQLConnection.UserName := 'mysqldvp'
;
(* Lecture du mot de passe *)
if
InputQuery('Connexion à la base de données'
, 'Tapez votre mot de passe :'
, True
, LPassword)
then
begin
(* Connexion à la base de données *)
MySQLConnection.Password := LPassword;
try
MySQLConnection.Connected := True
;
SQLTransaction1.Active := True
;
except
on
e: ESQLDatabaseError do
begin
(* Erreur renvoyée par MySQL : fin de programme *)
MessageDlg('Erreur de connexion à la base de données :'#10#10#13
+
IntToStr(e.ErrorCode) + ' : '
+ e.Message
+
#10#10#13'Fin de programme.'
, mtError, [mbOk], 0
);
Close;
end
;
on
e: EDatabaseError do
begin
(* Erreur de connexion : fin de programme *)
MessageDlg('Erreur de connexion à la base de données.'#10#10#13'Fin de programme.'
, mtError, [mbOk], 0
);
Close;
end
;
end
;
end
else
(* Pas de mot de passe : fin de programme *)
Close;
end
;
Par exemple, si la base de données n'existe pas :
La gestion de l'exception EDatabaseError suit celle de ESQLDatabaseError. Le principe général est de gérer en cascade les exceptions de la plus spécialisée à la moins spécialisée : ainsi, si la connexion au serveur MySQL est possible, les erreurs MySQL seront traitées en priorité (ESQLDatabaseError).
Vous avez certainement remarqué que les messages d'erreur sont en anglais, la langue par défaut de Lazarus. Pour obtenir les messages en français, je vous renvoie au tutoriel de Gilles Vasseur sur l'internationalisation d'une application.
IV-B. Récupération de la liste des tables▲
À présent, nous allons faire en sorte que l'utilisateur puisse choisir une des tables contenues dans la base de données pour en afficher le contenu.
Sélectionnez un composant TListBox dans l'onglet Standard et déposez-le en bas et à gauche sur la fiche du projet. Dans l'inspecteur d'objets, renommez sa propriété Name en lbTables.
Dans l'éditeur de source, allez dans la déclaration du type TMainForm (que Lazarus a automatiquement déclaré ainsi lorsque vous avez appelé MainForm votre fiche principale). Dans la section private, ajoutez cette procédure :
procedure
ShowTables;
Pressez la combinaison de touches Shift-Control-C pour que Lazarus crée la procédure dans la section implementation et complétez cette dernière :
procedure
TMainForm.ShowTables;
begin
SQLQuery1.Close;
SQLQuery1.SQL.Text := 'SHOW TABLES;'
;
SQLQuery1.Open;
while
not
SQLQuery1.EOF do
begin
lbTables.Items.Add(SQLQuery1.Fields[0
].AsString);
SQLQuery1.Next;
end
;
(* Sélection du premier élément *)
if
SQLQuery1.RecordCount > 0
then
lbTables.ItemIndex := 0
;
end
;
Tout d'abord, la commande SQL qui permet de retourner la liste des tables d'une base de données est SHOW
TABLES
;.
Cette commande est passée au composant SQLQuery1 dans sa propriété SQL.Text (comme nous avions fait dans notre premier exemple) et est exécutée dans la méthode Open. Ensuite, nous bouclons pour que chaque nom de table retourné par la requête soit ajouté dans la listbox lbTables.
Placez l'appel de cette méthode privée ShowTables dans le bloc try de la méthode événementielle FormActivate de la fiche principale :
procedure
TMainForm.FormActivate(Sender: TObject);
var
LPassword : String
;
begin
{ . . . }
if
InputQuery('Connexion à la base de données'
, 'Tapez votre mot de passe :'
, True
, LPassword)
then
begin
MySQLConnection.Password := LPassword;
try
MySQLConnection.Connected := True
;
SQLTransaction1.Active := True
;
ShowTables; // ***** AJOUT *****
except
{ . . . }
end
;
end
else
(* Pas de mot de passe : fin de programme *)
Close;
end
;
Vous pouvez faire un test d'exécution à ce stade, pour vérifier que la liste des tables est bien chargée dans la listbox :
IV-C. Affichage du contenu de la table sélectionnée▲
Comme dans le premier exemple, nous allons afficher le contenu d'une table dans un composant TDBGrid, de l'onglet Data Controls, que vous déposez en haut et à gauche de votre fiche principale, et dont vous agrandissez la largeur jusqu'à la limite droite de la fiche et la hauteur jusqu'à un peu au-dessus de la listbox.
L'utilisation du DBGrid nous conduit à ajouter également un composant TDataSource, de l'onglet Data Access :
Toujours comme dans l'exemple 1, la propriété DataSet du TDataSource doit être affectée à SQLQuery1, et la propriété DataSource du TDBGrid à DataSource1.
Dans l'inspecteur d'objets, sélectionnez la listbox lbTables, allez dans l'onglet Événements, trouvez l'événement OnSelectionChange et cliquez sur les trois points en regard de celui-ci pour créer une méthode événementielle qui s'exécutera à chaque changement de choix de table :
procedure
TMainForm.lbTablesSelectionChange(Sender: TObject; User: boolean
);
begin
SQLQuery1.Close;
SQLQuery1.SQL.Text := 'SELECT * FROM '
+ lbTables.GetSelectedText + ';'
;
SQLQuery1.Open;
end
;
Simplement, la requête SELECT
*
FROM
est complétée par le nom de la table sélectionnée dans la listbox.
Testez l'application en l'état :
Le fait de sélectionner une autre table charge automatiquement son contenu dans le DBGrid.
Si vous êtes très attentif(ve), vous verrez très fugacement apparaître la liste des tables dans le DBGrid, juste avant que le contenu de la première table s'affiche. C'est dû au fait que le DBGrid, via le DataSource, affiche le contenu du dataset du SQLQuery. Pour éviter cette brève apparition, il aurait fallu ne pas lier le DBGrid au DataSource dans ses propriétés et ajouter cette instruction juste après l'exécution de ShowTables (dans la méthode FormActivate) : DBGrid1.DataSource := DataSource1;.
IV-D. Permettre de modifier les données▲
Nous avons laissé un peu de place entre le bord supérieur de la listbox et le bord inférieur du DBGrid pour pouvoir y insérer une barre d'outils de navigation : un composant TDBNavigator (que l'on trouve au début de l'onglet Data Controls). Centrez-le ou donnez-lui la même largeur que le DBGrid :
Affectez DataSource1 à sa propriété DataSource. Dans l'inspecteur d'objets, vous pouvez choisir quels boutons vous voulez afficher dans le DBNavigator, dans sa propriété VisibleButtons. Décochez notamment le bouton nbRefresh, qui n'a aucune utilité dans notre exemple.
Exécutez à nouveau l'application et expérimentez la navigation avec les boutons verts, mais aussi l'insertion de lignes, la suppression, etc. Vous pouvez y aller franchement et tout casser : les modifications que vous faites affectent juste le contenu du DBGrid, mais pas la base de données elle-même. D'ailleurs, si vous changez votre choix de table dans la listbox, vous voyez qu'à chaque réaffichage la table est restaurée dans son état d'origine.
La dernière étape va être d'enregistrer les modifications dans la base de données.
Ne martyrisez pas trop la base de données dans les tests que vous ferez dorénavant, car nous en aurons encore besoin pour la suite du tutoriel.
Nous allons donc écrire une méthode privée qui va s'occuper de mettre à jour les données des tables dans la base. Cette méthode sera appelée à chaque fois que l'on changera de table dans la listbox, ainsi qu'à la fermeture de l'application.
Retournez dans l'éditeur de source et ajoutez cette méthode dans la section private de TMainForm :
procedure
CommitChanges;
Un petit Shift-Control-C pour son implémentation :
procedure
TmainForm.CommitChanges;
begin
if
SQLTransaction1.Active
then
try
SQLQuery1.ApplyUpdates;
SQLTransaction1.Commit;
except
on
e: EDatabaseError do
begin
MessageDlg('Erreur d''enregistrement des modifications'
, mtError, [mbOk], 0
);
end
;
end
;
end
;
Appelons-la dans la méthode événementielle qui répond au changement de table dans la listbox :
procedure
TMainForm.lbTablesSelectionChange(Sender: TObject; User: boolean
);
begin
CommitChanges; // ***** AJOUT *****
SQLQuery1.Close;
SQLQuery1.SQL.Text := 'SELECT * FROM '
+ lbTables.GetSelectedText + ';'
;
SQLQuery1.Open;
end
;
Et dans la méthode qui répond à la fermeture de la fiche principale :
procedure
TMainForm.FormClose(Sender: TObject; var
CloseAction: TCloseAction);
begin
CommitChanges; // ***** AJOUT *****
SQLQuery1.Close;
if
SQLTransaction1.Active
then
SQLTransaction1.Active := False
;
if
MySQLConnection.Connected
then
MySQLConnection.Connected := False
;
end
;
Testez l'application ainsi modifiée (encore une fois, en y allant mollo pour garder utilisable la base de données). Inspectez le contenu de la base avec phpMyAdmin : vous constaterez que toutes les modifications y ont bien été répercutées.
IV-E. Code complet de l'exemple 2▲
unit
Main;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, mysql56conn, sqldb, FileUtil, Forms, Controls, Graphics,
Dialogs, StdCtrls, Grids, DBGrids, DbCtrls, db;
type
{ TMainForm }
TMainForm = class
(TForm)
DataSource1: TDataSource;
DBGrid1: TDBGrid;
DBNavigator1: TDBNavigator;
lbTables: TListBox;
MySQLConnection: TMySQL56Connection;
SQLQuery1: TSQLQuery;
SQLTransaction1: TSQLTransaction;
procedure
FormActivate(Sender: TObject);
procedure
FormClose(Sender: TObject; var
CloseAction: TCloseAction);
procedure
lbTablesSelectionChange(Sender: TObject; User: boolean
);
private
{ private declarations }
procedure
ShowTables;
procedure
CommitChanges;
public
{ public declarations }
end
;
var
MainForm: TMainForm;
implementation
{$R *.lfm}
{ TMainForm }
procedure
TMainForm.FormClose(Sender: TObject; var
CloseAction: TCloseAction);
(* Fermeture propre de la connexion avant la fin de programme *)
begin
(* Enregistrement des éventuelles modifications *)
CommitChanges;
(* Fermeture de la connexion *)
SQLQuery1.Close;
if
SQLTransaction1.Active
then
SQLTransaction1.Active := False
;
if
MySQLConnection.Connected
then
MySQLConnection.Connected := False
;
end
;
procedure
TMainForm.lbTablesSelectionChange(Sender: TObject; User: boolean
);
(* Sélection d'une table dans la listbox *)
begin
(* Enregistrement des éventuelles modifications *)
CommitChanges;
(* Chargement des données de la table choisie *)
SQLQuery1.Close;
SQLQuery1.SQL.Text := 'SELECT * FROM '
+ lbTables.GetSelectedText + ';'
;
SQLQuery1.Open;
end
;
procedure
TMainForm.ShowTables;
(* Chargement de la liste des tables dans la listbox *)
begin
SQLQuery1.Close;
SQLQuery1.SQL.Text := 'SHOW TABLES;'
;
SQLQuery1.Open;
while
not
SQLQuery1.EOF do
begin
lbTables.Items.Add(SQLQuery1.Fields[0
].AsString);
SQLQuery1.Next;
end
;
if
SQLQuery1.RecordCount > 0
then
lbTables.ItemIndex := 0
;
end
;
procedure
TMainForm.CommitChanges;
(* Enregistrement des modifications *)
begin
if
SQLTransaction1.Active
then
try
SQLQuery1.ApplyUpdates;
SQLTransaction1.Commit;
except
on
e: EDatabaseError do
begin
MessageDlg('Erreur d''enregistrement des modifications'
, mtError, [mbOk], 0
);
end
;
end
;
end
;
procedure
TMainForm.FormActivate(Sender: TObject);
(* Lecture du mot de passe et connexion à la base de données *)
var
LPassword : String
;
begin
(* Données de la connexion *)
MySQLConnection.HostName := '192.168.0.1'
;
MySQLConnection.DatabaseName := 'location'
;
MySQLConnection.UserName := 'mysqldvp'
;
(* Lecture du mot de passe *)
if
InputQuery('Connexion à la base de données'
, 'Tapez votre mot de passe :'
, True
, LPassword)
then
begin
(* Connexion à la base de données *)
MySQLConnection.Password := LPassword;
try
MySQLConnection.Connected := True
;
SQLTransaction1.Active := True
;
ShowTables;
except
on
e: EDatabaseError do
begin
(* Erreur de connexion : fin de programme *)
MessageDlg('Erreur de connexion à la base de données.'#10#13'Le mot de passe est peut-être incorrect ?'#10#10#13'Fin de programme.'
, mtError, [mbOk], 0
);
Close;
end
;
end
;
end
else
(* Pas de mot de passe : fin de programme *)
Close;
end
;
end
.
Téléchargez le projet mysql02 ici.