Ma première base de données avec C++ Builder

Laurent Berne

Le but de ce tutoriel est la réalisation d'une petite application incorporant une base de données. J'ai choisi aléatoirement comme sujet de faire un petit carnet d'adresses. Il s'agit en fait de la première application que j'ai faite avec C++Builder (à l'époque en version 3) pour interfacer une base de données. Le format de base de données choisi est Paradox, parce qu'il est à mon avis le plus simple à mettre en œuvre pour un débutant. ♪

- Titre : Ma première base de données avec C++ Builder

- Auteur : Laurent Berne

- Parution : 3 juin 2001

- Licence : Copyright ® 2001 Laurent Berne. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- Avant-propos

- I. Rappel sur les bases de données

- II. Première étape : la création du fichier Paradox

- III. Deuxième étape : la création du projet et sa mise en forme

- IV. Troisième partie : codage de la partie fonctionnelle

Avant-propos

Les prérequis pour ce tutoriel sont :

- savoir créer et sauvegarder un projet ;

- la connaissance des composants TButton, TEdit, TMaskEdit, TPanel et TMainMenu, la classe AnsiString ;

- avoir entendu parler de base de données et si possible en avoir utilisé ;

- il est nécessaire d'avoir le BDE d'installé (pour C++ Builder 5, il faut donc la version professionnelle minimum…).

Dans ce tutoriel, je n'utiliserai pas les contrôles visuels orientés « Base de données » comme les TDBEdit, TDBMemo, TDBNavigator… Néanmoins, j'y ai placé un TDBGrid, mais en lecture seule, qui ne sert qu'à visualiser les opérations que je fais sur la table. Le but de ce tutoriel est de voir comment on manipule par du code C++ une base de données, par l'intermédiaire du composant TTable.

I - Rappel sur les bases de données

C'est une façon de stocker l'information et d'établir des relations entre les différentes données. Par exemple, on a tous pesté par les publicités qu'on reçoit dans notre boîte aux lettres, nous précisant que l'on vient de gagner une machine à laver… Ce qui est surprenant, c'est que la société émettrice possède nos coordonnées précises, ainsi que celles de nos voisins, amis, famille, etc.
Dans sa forme la plus basique, nous pouvons l'imaginer comme un tableau. Chaque colonne serait un renseignement comme « Nom, Prénom, adresse… » et chaque ligne correspondrait à une personne.
D'un point de vue informatique, une base de données est constituée de un ou plusieurs fichiers constitués de une ou plusieurs tables (= nos tableaux vus plus haut). Car suivant la complexité des renseignements et leur caractère répétitif, on peut facilement envisager de relier deux tables entre elles. On parle alors de base de données relationnelle.
Bien sûr il n'existe pas un seul format de base de données sur le marché… Interbase, Oracle, Access, Paradox sont des noms très répandus dans les entreprises. On les choisit suivant le degré de complexité de la base, le nombre d'utilisateurs simultanés à utiliser la base… et bien sûr la grosseur du porte-monnaie !
Pour notre exemple, j'ai choisi Paradox pour sa simplicité : un fichier correspond à une seule et unique table, au contraire d'Access par exemple où toutes les tables sont stockées dans le même fichier…
Une base de données Paradox est constituée d'un ou plusieurs fichiers Paradox, normalement situés dans le même répertoire.
Parfois suivant les ouvrages consacrés aux produits Borland, on parlera d'Alias de base de données. C'est une notion que j'ai choisi d'ignorer, car avant de jongler avec les différentes identités d'une base de données, il est préférable de voir comment elle fonctionne.

II - Première étape : la création du fichier Paradox

Pour cela nous utiliserons l'utilitaire fourni par Borland, appelé « Module Base de données » (pour ceux qui ont une version anglaise : Database Desktop).

On choisit le menu « Fichier->Nouveau->Table… ». On doit avoir l'écran suivant :

On choisit le format Paradox 7 et on valide.

Une nouvelle boite de dialogue apparaît :

Elle sert à spécifier les champs de notre base de données.
Il faut remplir de manière à obtenir ceci :

Une fois encore, j'ai choisi que tous les champs seraient du type alphanumérique dans un souci de facilité. Seul le premier champ est du type « numérique autoincrémenté ».
On enregistre la table sous le nom « Data.db » et on la place dans le même répertoire que notre futur projet.

III - Deuxième étape : la création du projet et sa mise en forme

Sous C++ Builder, on va créer un nouveau projet et tout de suite l'enregistrer (un bon réflexe !). J'ai nommé le projet « Carnet », mais chacun fait ce qu'il veut… L'unité principale est sauvegardée sous « UnitMain ». Ma Form s'appelle « Main ».

Voici son aspect général :

Voici la déclaration des composants telle qu'on pourrait la voir dans le header « Main.h ».

class TMain :  public TForm
{
__published:      // Composants gérés par l'EDI

TDBGrid *DBGrid1;
TGroupBox *GroupBox1;
TTable *Table;
TDataSource *DataSource1;
TLabel *Label1;
TLabel *Label2;
TLabel *Label3;
TLabel *Label4;
TLabel *Label5;
TLabel *Label6;
TLabel *Label7;
TLabel *Label8;
TButton *BoutonValider;
TButton *BoutonAnnulerSaisie;
TButton *BoutonSupprimer;
TButton *BoutonNouveau;
TButton *BoutonModifier;
TButton *BoutonRecherche;
TMaskEdit *TelMaskEdit;
TMaskEdit *CPMaskEdit;
TEdit *VilleEdit;
TEdit *AdresseEdit;
TEdit *PrenomEdit;
TEdit *NomEdit;
TEdit *FiltreNom;
TEdit *RechercheNomEdit;
TMainMenu *Menu;
TMenuItem *Fichier1;
TMenuItem *Nouveau1;
TMenuItem *Modifier1;
TMenuItem *Quitter1;
TMenuItem *Aide1;
TMenuItem *Apropos1;
TButton *BoutonSuivant;
TButton *BoutonPrecedant;
TButton *BoutonFirst;
TButton *BoutonLast;
...

Je recommande vivement pour la compréhension du tutoriel de nommer les composants de la même façon.

La Form Main fait 650 en largeur (Width) et 230 en hauteur (Height).

Les propriétés importantes du TTable sont :

- Active : cette propriété détermine si effectivement l'application est reliée à la table ou non ;

- DatabaseName : c'est le nom de la base de données ou de son Alias ;

- TableName : c'est le nom de la table. Chaque composant TTable ne peut être relié qu'à une seule et unique table. Dans notre cas (le format Paradox) la table correspond à un fichier.

Pour paramétrer notre TTable (« Table ») :

1. On détermine le nom de notre base de données dans la propriété DatabaseName. Par souci de simplicité, nous allons laisser la base de données dans le même répertoire que notre application. Cette propriété restera donc vide, ce qui signifie au BDE qu'il doit chercher la base de données dans le répertoire de l'application ;

2. On choisit la table que l'on va contrôler par le composant. Si notre propriété DatabaseName est correctement remplie, le nom de notre fichier créé précédemment devrait apparaître dans la listbox. On le sélectionne ;

3. On met la propriété Active à true.

Si tout est correctement configuré, il ne se passe… rien (par contre il y aura des messages d'erreur dans le cas contraire). Voici une vue de l'inspecteur d'objets sur le TTable :

On en profite pour paramétrer le TDBGrid qui va nous servir à visualiser nos manipulations. Pour cela, il faut paramétrer d'abord le TDataSource ! Il sert de lien entre les composants visuels comme les TDBEdit, TDBMemo… et les dérivés de TDataSet comme TTable, TQuery, TADOTable…

Les propriétés importantes du TDataSource sont :

- AutoEdit : sert à déterminer si tous les contrôles visuels peuvent on non modifier la base de données. Dans notre cas, la réponse est false, car la TDBGrid ne sert qu'à visualiser ;

- DataSet : sert à déterminer le composant qui est « relié » à la table, dans notre cas le TTable. Il doit logiquement apparaître dans la listbox. Il faut le sélectionner bien sûr.

Enfin dans la TDBGrid, il faut lui signaler par sa propriété DataSource sur quel composant DataSource elle doit se connecter. Prudence étant mère de sûreté, j'ai mis la propriété ReadOnly à true.
Sur l'exemple j'ai « enjolivé » la grille en modifiant ses propriétés dans ses propriétés Columns et Options. Là le bon goût étant subjectif, je laisse à chacun le soin de faire les modifications qu'il jugera nécessaires…

Voilà la partie visuelle terminée, on va pouvoir passer à…

IV - Troisième partie : codage de la partie fonctionnelle

Voyons un peu les principes de fonctionnement de l'application. Son but est de gérer un petit agenda et numéro de téléphone. Nous devons donc prévoir :

- un moyen de saisir les données (pour chaque nouvelle personne que nous rencontrons…) ;

- un moyen de supprimer les données (en cas de brouilles mortelles et irrémédiables…) ;

- un ou plusieurs moyens de retrouver les données (si on ne peut pas le consulter, un carnet ne sert à rien…) ;

- un moyen de modifier les données (en cas de déménagement…).

Je rappelle que j'ai fait cette petite application dans un but didactique, mais aussi dans l'optique de m'en servir (et elle m'a servi longtemps…). J'avais donc rajouté quelques fioritures, par exemple rétracter la zone de saisie.

C'est pour cela que j'ai placé dans l'événement OnCreate de la Form le code suivant :

void __fastcall  TMain::FormCreate(TObject *Sender)
{
Height=235; // Retracte la fenêtre ..
FiltreNom->Text=""; // Vide le texte de FiltreNom
RechercheNomEdit->Text=""; // Vide le texte de RechercheNomEdit
}

Notre application doit comporter des méthodes de modification, insertion, navigation dans les données.

Avant d'aller plus loin, il est intéressant de comprendre comment le TTable accède aux données.
Le TTable est un objet qui dérive de l'objet TDataSet. Il implémente donc des procédures de navigation et d'ajout/suppression d'enregistrements.
Comme on l'a vu dans notre rappel sur les bases de données, on peut facilement visualiser une table comme un tableau où les titres des colonnes seraient les noms des champs. C'est ainsi que notre composant TDBGrid représente notre fichier Paradox. Sur cette même TDBGrid, on remarque sur la gauche du composant un petit curseur. Il indique l'enregistrement sur lequel pointe le TTable. Car le composant TTable ne pointe que sur un seul et unique enregistrement. Donc il faudra impérativement positionner le TTable sur l'enregistrement que l'on veut modifier. Il y a pour cela plusieurs façons de faire. La TDBGrid en implémente une : il suffit de sélectionner l'enregistrement pour que le TTable se positionne dessus. Toutefois si cette méthode est simple à mettre en œuvre, elle est frustrante pour le développeur qui ne maîtrise pas le comportement du TTable.
Sur la Form, nous avons placé quatre boutons : Suivant, Précédent, Premier Enregistrement, Dernier Enregistrement, respectivement nommés : BoutonSuivant, BoutonPrecedent, BoutonFirst, BoutonLast.

Voici le code qu'il faut placer dans chaque événement OnClick de ces boutons :

void __fastcall TMain::BoutonSuivantClick(TObject *Sender)
{
Table->Next(); // Déplace le curseur sur l'enregistrement suivant
}
//---------------------------------------------------------------------------

void __fastcall TMain::BoutonPrecedantClick(TObject *Sender)
{
Table->Prior(); // Déplace le curseur sur l'enregistrement précédent
}
//---------------------------------------------------------------------------

void __fastcall TMain::BoutonFirstClick(TObject *Sender)
{
Table->First(); // Déplace le curseur sur le premier enregistrement
}
//---------------------------------------------------------------------------

void __fastcall TMain::BoutonLastClick(TObject *Sender)
{
Table->Last(); // Déplace le curseur sur le dernier enregistrement
}

À l'exécution (et sous réserve que votre table contienne des enregistrements !), on visualise très bien le résultat de ces méthodes grâce au curseur de la TDBGrid. Les méthodes Next() et Prior servent à se déplacer d'enregistrement en enregistrement dans le sens croissant ou décroissant. Les méthodes First() et Last() quant à elles servent à se positionner respectivement sur le premier et le dernier enregistrement.

Maintenant que nous savons nous déplacer dans les données, nous allons pouvoir y accéder, c'est-à-dire récupérer les valeurs pour les utiliser dans nos programmes. Pour cela, regardons de plus près le code placé dans l'événement OnClick de BoutonModifier. Dans notre petit programme, ce bouton sert à afficher les données que nous voulons modifier.

void __fastcall TMain::BoutonModifierClick(TObject *Sender)
{
// Boucle qui enclenche la procédure de modification d'une fiche client
if((Table->RecordCount==0)&&(Table->State!=dsInsert))
return; // quitte la procédure si la table est vide et qu'on est pas en insertion
BoutonSupprime->Enabled = true; // Activation du bouton Supprimer
BoutonNouveau->Enabled= false; // Désactive le bouton OK
Fichier1->Enabled = false; // Désactive le menu fichier
Height = 340; // Agrandit la fenêtre
NomEdit->Text=Table->FieldByName("Nom")->AsString; // Récupère le nom et le place dans le TEdit
PrenomEdit->Text=Table->FieldByName("Prenom")->AsString; /* Récupère le prénom et le place dans
le TEdit */
AdresseEdit->Text=Table->FieldByName("Adresse")->AsString; /* Récupère l'adresse et le place dans
le TEdit */
VilleEdit->Text=Table->FieldByName("Ville")->AsString; // Récupère la ville et le place dans le TEdit
TelMaskEdit->Text = Table->FieldByName("Tel")->AsString; /* Récupère le code postal et le place dans
le TMaskEdit */
CPMaskEdit->Text = Table->FieldByName("CP")->AsString; // Récupère le téléphone et le place dans
le TMaskEdit */
}

Cette partie du code sert uniquement à remplir les TEdit avec le contenu du champ. Pour cela on utilise la méthode *TField __fastcall FieldByName(const AnsiString FieldName) de la classe TTable. Elle renvoie un pointeur sur un objet TField. Comme l'objet TField sert à accéder à un champ d'une base de données… à partir de cet objet on récupère la propriété AsString que l'on copie dans la propriété Text du TEdit (ou TMaskEdit…).

Cela sous-entend que l'on s'est positionné avant de vouloir lire les données.

Bien sûr il y a aussi quelques petites fioritures aussi avec les activations de boutons et l'agrandissement de la fenêtre pour afficher les zones de modifications.
On remarquera aussi le test qui vérifie qu'il y a quelque chose à modifier. La propriété RecordCount donne le nombre d'enregistrements dans la table. Si la table est vide, il n'y a rien à modifier, sauf si on insère le premier enregistrement vide. C'est pour cela que l'on teste aussi la propriété Table->State pour savoir si on est en mode insertion… Dans le cas contraire, on quitte la boucle purement et simplement.

Une fois les données affichées et les modifications faites, il nous faut enregistrer notre travail. Pour cela, nous allons ajouter sur l'événement OnClick de BoutonValider le code suivant :

void __fastcall TMain::BoutonValiderClick(TObject *Sender)
{
Table->Edit(); // Positionne la table en mode Edition
Table->FieldByName("Nom")->Value=NomEdit->Text; //Place le nom dans le TEdit dans la table
Table->FieldByName("Prenom")->Value = PrenomEdit->Text ; //Place le prénom dans le TEdit dans la table
Table->FieldByName("Adresse")->Value = AdresseEdit->Text ; //Place l'adresse dans le TEdit dans la table
Table->FieldByName("Ville")->Value = VilleEdit->Text; //Place la ville dans le TEdit dans la table
Table->FieldByName("Tel")->Value = TelMaskEdit->Text; //Place le téléphone dans le TEdit dans la table
Table->FieldByName("Cp")->Value = CPMaskEdit->Text; //Place le code postal dans le TEdit dans la table
Table->Post(); // Valide l'édition dans la base de données

Fichier1->Enabled = true; // Active le menu

BoutonSupprime->Enabled= true; //Active le bouton Supprimer
BoutonNouveau->Enabled= true; //Active le bouton Nouveau
BoutonModifier->Enabled= true; //Active le bouton Modifier
Height = 235; // Retracte la fenêtre
}

Ici nous accédons au champ par la propriété Value. Ce qu'il faut surtout remarquer, c'est que l'on place d'abord la table en mode « édition » avec la méthode Table->Edit(). On signifie ainsi que les valeurs que nous plaçons dans les champs vont remplacer celles qui étaient stockées dans le fichier. Une fois que l'on a placé toutes nos valeurs, on signale au composant TTable qu'il doit valider les changements par la méthode Table->Post().

Occupons-nous de la partie « Création d'un nouvel enregistrement ». Pour cela sur l'événement OnClick de BoutonNouveau on rajoute la procédure suivante :

void __fastcall TMain::BoutonNouveauClick(TObject *Sender)
{
Table->Append(); //Ajoute un enregistrement vide et se positionne dessus
BoutonModifierClick(this); // appelle la méthode de modification
}

La méthode void Append(void) rajoute un enregistrement vide à la fin de la table et surtout positionne le curseur sur le nouvel enregistrement (c'est d'un compliqué…).
Une fois le nouvel enregistrement créé, on appelle la méthode BoutonModifierClick qu'on a définie plus haut.

Enfin, rajoutons maintenant la suppression d'enregistrements. On place dans l'événement OnClick de BoutonSupprimer le code suivant :

void __fastcall  TMain::BoutonSupprimerClick(TObject *Sender)
{
// Appel de la fonction API Win32 boite de message pour confirmation
int rep= MessageBox(Handle,"Êtes-vous sûr de vouloir supprimer cette personne ?",
"Avertissement",MB_OKCANCEL|MB_ICONSTOP ) ;
if (rep ==IDOK) // teste si réponse OK
Table->Delete(); // Suppression de l'enregistrement

BoutonNouveau->Enabled= true; //Activation du bouton nouveau
Fichier1->Enabled = true; // active l'item du menu Fichier
BoutonSupprime->Enabled = false; // Désactive le Bouton Supprime
Height = 235; // Taille de la fiche à 220 pixels
}

La suppression de l'enregistrement se fait par la méthode Table->Delete(). Bien évidemment il faut se positionner d'abord sur l'enregistrement à supprimer.
De plus, le BDE ne demandant aucune confirmation pour éviter les erreurs, c'est à nous de le coder ! Pour cela on utilise la fonction de l'API MessageBox(…) qui invoque une boîte de dialogue.

Il existe deux autres fonctionnalités intéressantes du composant TTable. Imaginons que nous avons un carnet d'adresses de ministre. Pour retrouver le numéro de téléphone du coiffeur, s’il faut appuyer quelques dizaines de fois sur le bouton « Suivant », on va se lasser très vite.

On va donc étudier la méthode Locate(…) à l'aide du code suivant que l'on saisit dans l'événement OnClick de BoutonRecherche (celui où est marqué Go !).

void __fastcall TMain::BoutonRechercheClick(TObject *Sender)
{

if (!RechercheNomEdit->Text.IsEmpty()) // teste pour voir si le Edit est vide
{
TLocateOptions Option; // Créer un objet option
Option << loCaseInsensitive; // Rajout de l'option "ne fais pas attention à la casse"
Table->Locate("Nom",RechercheNomEdit->Text,Option); /* Recherche l'enregistrement suivant
les critères */
}
}

La méthode Locate permet de trouver un enregistrement à partir d'un champ. Ici nous recherchons un nom. Donc quand on va appeler la méthode Locate, le composant TTable va se positionner sur le premier enregistrement dont le champ « Nom » a pour valeur le nom que l'on a saisi dans le TEdit. La partie la plus délicate est l'instanciation d'un objet TLocateOptions. Cet objet sert à déterminer les options de tris. Deux choix sont possibles : ignorer la casse, et regarder partiellement la valeur. Les deux choix peuvent être simultanés.

La deuxième fonctionnalité intéressante est le filtre. Nous allons implémenter dans notre petit programme un filtre sur les noms.
Plaçons ce code dans les événements OnChange et OnExit du TEdit FiltreNom.

void __fastcall TMain::FiltreNomChange(TObject *Sender)
{
String Filtre = "Nom = '" + FiltreNom->Text + "*' "; /* Construction du Filtre avec le texte saisi dans
la zone TEdit*/
if (FiltreNom->Text.IsEmpty()) // Teste s'il n'y a rien dans le TEdit
{
Table->Filtered=false; // Arrête le filtrage de la table
return; // quitte la méthode
}
Table->Filter = Filtre; // Mise en place du filtre
Table->Filtered = true; // Activation du filtre
}

La construction du filtre se base sur le modèle suivant : Nom du champ, Opérateur de comparaison, Valeur de test.
Concrètement le champ est une chaîne de caractères AnsiString que l'on place dans la propriété Filter du composant TTable.
Une fois la propriété Filter renseignée, il faut activer le filtre en mettant la propriété Filtered à true.
On remarquera ici l'usage du caractère '*' dans le filtre qui est un caractère générique.

Et voilà il n'y a plus qu'à compiler le tout et observer.

Normalement on doit obtenir ceci :

On va maintenant remplir notre carnet d'adresses, on clique sur le bouton Nouveau

Une fois que nous aurons entré plus d'enregistrements, nous pourrons tester une à une les méthodes que nous avons implémentées.

Bon courage

Sources

Laurent Berne, consultant
(TeamB-fr)