Accueil
Rechercher:
sur developpez.com sur les forums
Forums | Tutoriels | F.A.Q's | Participez | Hébergement | Contacts
Club Emploi Blogs   TV   Dév. Web PHP XML Python Autres 2D-3D-Jeux Sécurité Windows Linux PC Mac
Accueil Conception Java DotNET Visual Basic  C  C++ Delphi MS-Office SQL & SGBD Oracle  4D  Business Intelligence
FORUMS DELPHI F.A.Q DELPHI TUTORIELS DELPHI LIVRES COMPOSANTS SOURCES DEFI TELECHARGEZ DELPHI TV

LES FICHIERS. DESCRIPTIONS ET UTILISATION.
1° partie : les bases.



Date de publication : 13/10/2006 , Date de mise à jour : 13/10/2006

Par Philippe GORMAND (Contributions)
 

Comme beaucoup de choses, on utilise les fichiers sans bien savoir ce que c'est ni comment ils sont structurés. Que signifie l'extension, qu'est ce qu'un "type fichier" qu'est ce qu'un format de fichier ? Dans cette première partie s'adressant essentiellement aux débutants, je propose de construire en mode pas à pas un petit agenda téléphonique avec photo d'identité et commentaire.

1. Préambule : Qu'est ce qu'un fichier ?
2. Pas de fichier sans un support matériel.
3. Les différents formats de fichiers et leurs extensions.
3-1. Fichier texte.
3-2. Fichier binaire.
3-3. Les fichiers de type texte ne portant pas l'extension ".txt".
3-3-1. Le fichier du type tableur genre CSV (avec programme d'exemple).
3-3-2. Le fichier INI .
3-3-3. Le fichier HTML.
3-4. Qu'est ce que le "Rich text file" ou "fichier texte enrichit" portant l'extension ".rtf" ?
3-5. Les fichiers avec entête et les fichiers sans entête.
3-6. Cas particulier, le fichier avec l'extension ".RAW".
4. Les routines de gestions de fichiers.
4-1. Création d'un fichier : Quelle méthode utiliser ?
4-2. Les principales routines de gestion de fichier en PASCAL.
5. Fichiers typés et non typés.
5-1. Déclaration d'un identifiant de fichier
5-2. Les structures dans les fichiers
5-3. Organisation d'un fichier
6. Les types String et AnsiString dans les structures.
6-1. Pourquoi avoir conservé l'ancien type String et créé des automatismes sur l'usage des pointeurs ?
7. Continuons avec une fonction simple, copie de fichier de n'importe quel type.
8. Gestion d'un fichier texte.
9. Gestion d'un fichier binaire
10. Mise en pratique d'un fichier binaire, création d'un agenda téléphonique.
10-1. Mettons en pratique le mécanisme, avec une saisie, une navigation et un enregistrement des données.
10-2. Comment supprimer un enregistrement ?
11. Qu'est ce qu'un flux ?
12. Reprenons notre agenda téléphonique et ajoutons une photo en face de chaque membre.
13. Continuons notre didacticiel avec l'ajout d'un commentaire pour chaque élément.
14. Tri des données.
15. Dimensionnement de l'image au format 150 x 150.
16. Ajoutons une procédure de chargement des images JPEG.
17. Modification de la procédure de chargement de l'image pour plus de sécurité.
18. Conclusion des exercices.
19. Gestion des fichiers et sécurité.
20. Complément. Quelques fonctions et procédures utiles.
21. Trucs et astuces.
21-1. Connaître la taille en octets de n'importe quel fichier.
21-2. Imposer le dossier par défaut de notre agenda au démarrage du programme.
21-3. Empêcher l'effacement accidentel d'un fichier.
21-4. Si vous voulez changer la date et l'heure de création du fichier.
22. Le fichier BitMap démystifié.
22-1. Structure d'un fichier BitMap.
22-2. Exemple d'une palette de conversion en 256 couleurs.
22-3. Programme permettant de connaître les caractéristiques d'une image BitMap sans charger tout le fichier.
23. Liste des codes d'erreurs Entrées/Sorties DELPHI et WINDOWS
24. Sources des exercices.
25. Liens divers et bibliographie
26. Que réserve la seconde partie du tutoriel ?
27. L'article complet au format PDF


1. Préambule : Qu'est ce qu'un fichier ?

La notion de fichier vient de l'époque où des données concernant un individu ou un objet particulier, étaient conservées sur des fiches cartonnées comme une fiche médicale par exemple. Un fichier est une collection de fiches rangées dans un classeur. Avec l'avènement de l'informatique, le terme et l'idée de fiches a été conservé. Les données ne sont plus écrites sur une fiche cartonnée, mais sous une forme binaire sur un support magnétique (ou optique sur un CDROM).
Bien sur, la notion de fichier a énormément évoluée. Une fiche médicale représente bien une structure (ou enregistrement), alors qu'une lettre représente un simple fichier texte, une suite de caractères sans organisation particulière. Mais, pour une machine, le clavier est un fichier d'entrées et la sortie vers imprimante est un fichier de sortie.

Un fichier a donc deux aspects, un aspect logique (ce qu'il représente) et un aspect matériel, le support sur lequel il est enregistré. On parle aussi de fichier virtuel. Avec tous les termes qu'emploient les informaticiens, chacun allant de son jargon, les débutants ont de quoi se perdre. Je vais donc commencer par exposer de façon aussi simple que possible quels sont les différents supports utilisés par les fichiers.


2. Pas de fichier sans un support matériel.

Disque dur, disque souple, bande magnétique, mémoire volatile (RAM) ou clef USB, quelles différences ?

Pour avoir une existence réelle, un fichier doit être stocké sur un support matériel. Un disque dur, une disquette (disque souple) sont des supports magnétiques comme la bande. Les données sont enregistrées sous forme binaire (0-1) dans de minuscules aimants dont la polarité représente 0 ou 1. Mais il y a aussi les supports électroniques, les mémoire vives (RAM) et les mémoires genre clef USB, composées de transistors ayant un état passant ou un état bloquant représentant le 0 ou le 1. Ce qu'on appelle fichier virtuel est tout simplement un fichier stocké dans la mémoire vive de l'ordinateur. Son existence cesse quand on éteint l'ordinateur.

Autres types de supports de fichiers, le CDROM et le DVD. Là, c'est sous une forme optique que sont enregistrées les données.

Description simplifiée d'un disque dur ou d'un disque souple (disquette). C'est un disque magnétique formaté en un certain nombre de pistes parallèles. Chaque piste est découpée en secteurs pouvant contenir potentiellement 512 ou 1024 octets.
Description simplifiée d'un CDROM ou DVD. C'est un disque optique ne contenant qu'une seule piste en spirale. Un CDROM de 650 MO a une piste de 6 Km de long. La piste à une largeur de 0,6µm et l'espace entre deux spires est de 1,6µm.




Je ne parlerais pas d'avantage de l'aspect matériel ni comment les données sont stockées sur les supports amovibles ou rotatifs, car les systèmes de contrôle de rotation, de validité de lecture et d'écritures sont particulièrement complexes. Il ne s'agit plus d'informatique, mais d'électronique et d'électromécanique.

Comment est organisé un disque dur ou un disque souple ?

3 Parties les composent. Le secteur de Boot, la table d'allocation de fichier et à la suite, l'espace devant contenir les données.
Le secteur de Boot est la partie qui permet d'identifier le disque lui-même et ses caractéristiques. Il peut en plus contenir un programme directement exécutable par le BIOS (BasicInputOutputSystem) de l'ordinateur, permettant le démarrage du système d'exploitation. La table d'allocation de fichier est en quelque sorte le catalogue des fichiers enregistrés sur le disque. Mais plus qu'un catalogue, c'est un répertoire.
Que contient ce répertoire ?




Fondamentalement, un fichier n'est que l'enregistrement d'une suite d'octets sur un support. Son emplacement sur le disque, son nom, sa taille, ses attributs (System, caché, lecture seule etc..) sont enregistrés dans ce répertoire qu'est la Table d'Allocation de Fichiers FAT, ou NTFS pour New Table Fat System.
Sur un CDROM, c'est un fichier en début de piste qui tient lieu de table d'allocation de fichier.

Remarque : Le BIOS (BasicInputOutputSystem) ou "Système d'entrées/sorties de base" est un programme stocké en dur sur la carte mère de l'ordinateur. C'est ce programme qui détermine tous les périphériques matériels de la machine (dont le disque dur), et qui détecte le programme d'exécution dans le secteur de BOOT si il existe.

A propos de BOOT, en connaissez vous le sens ? Vous en trouverez la définition ici Seteur de BOOT.
Je ne vous en dis pas plus... Surprise.


3. Les différents formats de fichiers et leurs extensions.

Si vous cherchez dans la littérature exposant les fichiers, vous trouverez plusieurs définitions. Les uns parlent de deux genres de fichiers, les fichiers textes et les fichiers binaires. On parle de format, de fichier typé et de fichier non typé.. Nous verrons progressivement les sens de ces termes.

L'extension d'un fichier exemple ".txt" ou ".jpg" fait partie de son nom et permet de savoir quel type de fichier il représente parmis certains standards, ou bien pour spécifier un type particulier créé selon le besoin.

Il y a bien longtemps, quand on programmait sous DOS, on distinguait (par convention) deux types de fichiers, le fichier texte et le fichier binaire. En fait ceux qu'on appelait fichier texte, étaient tout fichier composé de caractères ASCII éditable en mode dit "texte" comme le fait le bloc-note de windows. Les autres étaient appelé binaire car leur utilisation nécessitaient un programme particulier. A priori, un fichier texte représente un texte du point de vue littéraire. Mais sa structure est si simple qu'on a trouvé bien commode de l'utiliser à d'autres fins que de rédiger du courrier, comme nous le verrons plus loin.

Le format d'un fichier est la manière dont sont organisées les données qu'il contient.


3-1. Fichier texte.

Un fichier texte est une suite de caractères (d'octets) terminée par un retour chariot (code ASCII 13) qui est délimiteur de fin de ligne. Lors de l'écriture la procédure WriteLn se charge d'ajouter un retour chariot à la fin de chaque chaîne. A la lecture, la procédure ReadLn lira tous les caractères jusqu'au prochain retour chariot, ce retour chariot n'est pas copié dans la chaîne réceptrice. Certains programmes signalent la fin d'un fichier texte par le code EOF (code ASCII 26) appelé aussi "control Z", dans ce cas ce caractère n'est pas placé dans la chaîne réceptrice.


3-2. Fichier binaire.

On appel généralement "fichier binaire", tous les types de fichiers différents du type texte. Même si ce fichier représente du texte comme un fichier Word, c'est un fichier binaire. Il contient un ensemble de codes mélangés au texte pour définir la police de caractères, la couleur, les marges ect..


3-3. Les fichiers de type texte ne portant pas l'extension ".txt".

Comme je l'ai dit plus haut, la structure d'un fichier texte est si simple qu'on a trouvé bien commode de l'utiliser à d'autres fins que de rédiger du courrier. Je donne ici trois exemples de fichier ayant la même structure (du point vue informatique) que le fichier texte mais utilisé de façon particulière. Les fichier CSV, INI et HTML.


3-3-1. Le fichier du type tableur genre CSV (avec programme d'exemple).

Le fichier CSV est très ancien. Il a été utilisé par les tableurs. Les données enregistrées sont définies comme des champs, chaque champ est séparé du suivant par un caractère particulier et réservé à cet usage, le point-virgule. A la lecture, il suffit de compter le nombre de point virgule dans la chaîne pour connaître le nombre de champs. Mais on le trouve avec d'autres caractères comme séparateur de champ, la virgule ou la tabulation (code ASCII 9).

 Voici un exemple de contenu d'un fichier CSV :   Chien; Labrador;5;Noir;500 C'est par l'extension CSV que l'on sait que chaque mot (séparé du suivant par un ;) représente une donnée particulière. Ici, un animal du genre canin, de race Labrador, de 5 ans d'âge, de couleur noir et pour un prix de 500 euros.

Remarquez que EXCEL reconnaît ce type de fichier.

Exemple d'utilisation d'un fichier CSV. Vous trouverez à cette adresse le code source utilisant un "TStringGrid" permettant de charger, d'éditer, et d'enregistrer un fichier CSV.Source Exemple_CSV


3-3-2. Le fichier INI .

Un fichier INI est en quelque sorte une banque de données regroupée en sections. C'est un fichier texte. Les sections sont identifiées par des balises. Les balises sont les deux caractères "[" et "]".

Voici un exemple de fichier INI.

for 16-bit app support

[drivers]
wave=mmdrv.dll
timer=timer.drv

[mci]
[ASR1516$3.0.2]
Driver=dspli302.dll
Address=444
[ASR1532s$3.0.2]
Driver=dspli302.dll
Address=514
[ASR1016$3.0.2]
Driver=dspli302.dll
Address=345
[ASR1032s$3.0.2]
Driver=dspli302.dll
Address=666
[driver32]
[386enh]
device=C:\WINNT\system\wemu387.386
woafont=app850.FON
EGA80WOA.FON=EGA80850.FON
EGA40WOA.FON=EGA40850.FON
CGA80WOA.FON=CGA80850.FON
CGA40WOA.FON=CGA40850.FON


Il existe de très bons tutoriaux sur le sujet et de bons exemples dans la FAQ DELPHI du site.



3-3-3. Le fichier HTML.

Un fichier HTML est comme le fichier INI, purement et simplement un fichier texte. C'est son extension ".htm" ou ".html" qui permet de savoir qu'il doit être traité de façon particulière.

Le fichier HTML est composé de textes (au sens littéraire) entouré de Balises (des mots réservés au langage HTML) qui les feront apparaître avec une taille, un style et une couleur définit dans la balise.

Un fichier HTML ne contient jamais d'image mais des liens vers des adresses WEB qui permettent le chargement des images par le navigateur.

Comme sa structure est la même que celle d'un simple fichier texte, un fichier HTML peut aisément être édité et modifié avec le bloc note de WINDOWS.


3-4. Qu'est ce que le "Rich text file" ou "fichier texte enrichit" portant l'extension ".rtf" ?

Voici un texte tel qu'il apparaît à l'écran.



AVERTISSEMENTS CONSEILLES
Ce programme de retouche de photos, fonctionne
sous toutes les versions de WINDOWS 95, 98, ME,
NT4, 2000 et XP. Je l'ai testé avec des acquisitions
depuis un scanner à 7200 dpi sur une diapositive. le
résultat était une image de 62 millions de pixels.
Si vous rencontrez un problème quelconque je vous
serais gré de m'en faire part, en m'envoyant un
courrier électronique à philippe.gormand@free.fr
Vous pouvez m'écrire directement depuis mon site
WEB "Envoyer un message"
  URL -> http://philippe.gormand.free.fr/

Ce programme a été testé avec des images de plus
10 millions de pixels sur plusieurs ordinateurs
dont un PENTIUM I à 100 Mhz et avec 64 MOctets
de mémoire vive sous WINDOWS 95.

Pour fonctionner confortablement avec de grandes
images, un équipement de 128 Moctets de mémoire
est nécessaire.

Il reconnaît plus de 20 formats de fichier d'image
différent, mais ne peut enregistrer que 10, BMP
JPG,TGA etc...


Voici le contenu du fichier.

{\rtf1\ansi\ansicpg1252\deff0\deflang1036{\fonttbl{\f0\fmodern\fprq1 Courier New;}{\f1\fmodern\fprq1\fcharset0 Courier New;}{\f2\froman Times New Roman;}} {\colortbl ;\red255\green0\blue0;\red0\green0\blue0;\red0\green0\blue255;} {\*\generator Msftedit 5.41.15.1507;}\viewkind4\uc1\pard\f0\fs22\tab\tab\cf1\b AVERTISSEMENTS CONSEILLES\par \cf2\b0\par Ce programme de retouche de photos, fonctionne\par sous toutes les versions de WINDOWS 95, 98, ME, \par \f1 NT4, 2000 et XP. Je l'ai test\'e9 avec des acquisitions\par depuis un scanner \'e0 7200 dpi sur une diapositive. le\par r\'e9sultat \'e9tait une image de 62 millions de pixels.\par Si vous rencontrez un probl\'e8me quelconque je vous\f0\par \f1 serais gr\'e9 de m'en faire part, en m'envoyant un\f0\par \f1 courrier \'e9lectronique \'e0 \cf3\f0 philippe.gormand@free.fr\cf2\par \f1 Vous pouvez m'\'e9crire directement depuis mon site\f0\par WEB "Envoyer un message" \par \b URL ->\b0 \cf3 http://philippe.gormand.free.fr/\par \cf2\par \f1 Ce programme a \'e9t\'e9 test\'e9 avec des images de plus\f0\par 10 millions de pixels sur plusieurs ordinateurs \par \f1 dont un PENTIUM I \'e0 100 Mhz et avec 64 MOctets \f0\par \f1 de m\'e9moire vive sous WINDOWS 95.\f0\par \par Pour fonctionner confortablement avec de grandes\par \f1 images, un \'e9quipement de 128 Moctets de m\'e9moire \f0\par \f1 est n\'e9cessaire.\f0\par \par \f1 Il reconna\'eet plus de 20 formats de fichier d'image\f0\par \f1 diff\'e9rent, mais ne peut enregistrer que 10, BMP\f0\par JPG,TGA etc...\par \par \par \par \f2\fs20\par }

______________________________________________________________________

On constate donc, que ce fichier contient un ensemble de balises codées pour changer la façon dont le texte apparaîtra à l'écran. De plus, il peut contenir des images, identifiées par des balises particulières.

ATTENTION: WORD ne respecte pas le standard RichTextFile. Si vous voulez écrire un vrai RichTextFile utilisez WORDPAD.


3-5. Les fichiers avec entête et les fichiers sans entête.

Un simple fichier type texte portant l'extension ".txt" n'a pas d'entête. Le premier octet du fichier (octet zéro) est le premier caractère du texte. Il n'a pas besoin d'entête puisque sa structure est connue et répond à un standard très simple, une suite de caractères terminée par un caractère spécifique représentant la fin de chaque ligne. Mais pour certains fichiers, il est nécessaire de posséder certaines informations pour pouvoir utiliser les données. Un fichier vidéo (.AVI) par exemple, possède un entête de fichier. C'est une certaine quantité d'informations placée en début, et qui donne des renseignements sur le format de la vidéo (hauteur/largeur), la durée de la vidéo etc..

Voici la description de l'entête d'un méta fichier (dessin vectoriel) documentation MicroSoft.

The ENHMETAHEADER structure contains enhanced-metafile data such as the dimensions of the picture stored in the enhanced metafile, the count of records in the enhanced metafile, the resolution of the device on which the picture was created, and so on.

This structure is always the first record in an enhanced metafile.

typedef struct tagENHMETAHEADER { // enmh
   DWORD iType;
   DWORD nSize;
   RECTL rclBounds;
   RECTL rclFrame;
   DWORD dSignature;
   DWORD nVersion;
   DWORD nBytes;
   DWORD nRecords;
   WORD nHandles;
   WORD sReserved;
   DWORD nDescription;
   DWORD offDescription;
   DWORD nPalEntries;
   SIZEL szlDevice;
   SIZEL szlMillimeters;
   DWORD cbPixelFormat;
   DWORD offPixelFormat;
   DWORD bOpenGL;
} ENHMETAHEADER;


3-6. Cas particulier, le fichier avec l'extension ".RAW".

C'est un fichier sans entête. Une suite continue d'octets. Il ne répond à aucun standard. Il peut représenter un texte, une image ou n'importe quoi d'autre. Ce qu'il contient ne peut être lu correctement seulement si on connaît la structure des données enregistrées. Il est aussi définit comme "fichier de données brutes", très employé dans de nombreuses applications scientifiques, en particulier les machines de laboratoire faisant des acquisitions de données.


4. Les routines de gestions de fichiers.

Que nous offre DELPHI pour la gestion des fichiers ?

Tout d'abord, les mêmes fonctions et procédures qui existaient dès la création du TURBO-PASCAL par BORLAND, du moins de par leurs noms et similitudes d'utilisation. Ceci a été un très grand atout pour les développeurs quand ils sont passés à PASCAL-POUR-WINDOWS, puis à DELPHI. Le langage est resté le même, mais il a été considérablement enrichi.


Ensuite, les routines existantes dans le système d'exploitation WINDOWS qui sont accessibles. Les concepteurs de DELPHI l'on prévu.


4-1. Création d'un fichier : Quelle méthode utiliser ?

Pour pouvoir utiliser un fichier, il faut évidemment qu'il soit existant ou bien le créer. La création d'un fichier consiste à entrer dans la table d'allocation de fichier (sur le disque) les informations qui permettrons sont utilisation (nom, attributs, date etc…) et l'adresse de début des données du fichier sur le disque. La seule donnée primordiale est le nom du fichier. Il sera créé avec les attributs par défaut du système et avec la date du jour. NB : Le chemin du fichier accompagne son nom. Le système d'exploitation WINDOWS met à disposition du développeur toutes sortes de fonctions, tant pour la gestion graphique, les communications et la gestion de fichiers. Ce sont les fameuses API. La plupart de ces fonctions sont directement implémentés par DELPHI. On peut donc les utiliser (à priori) sans problème. Cependant certaines de ces fonctions sont d'une utilisation délicate ou particulièrement technique. Sans doute pour ces raisons, les créateurs de DELPHI ont choisit de conserver les routines PASCAL standard.

Pour créer un nouveau fichier il existe la fonction API "FileCreate" voici ce que nous dit l'aide de DELPHI.

function FileCreate(const FileName: string): Integer;

Description

La fonction FileCreate crée un nouveau fichier avec le nom spécifié. Si la valeur renvoyée est positive, la fonction s'est bien déroulée et cette valeur correspond au descripteur du nouveau fichier. Si la valeur renvoyée vaut -1, cela indique qu'une erreur s'est produite.

Remarque : L'utilisation de gestionnaires de variables de fichier Pascal non natif tels que FileCreate est déconseillée. Ces routines correspondent aux fonctions API Windows et renvoient des handles de fichier, pas des variables de fichier Pascal normales. Il s'agit de routines d'accès au fichier de bas niveau. Pour des opérations normales sur les fichiers, utilisez plutôt AssignFile, Rewrite et Reset.


Cela parait un peu confus. Comme si les concepteurs de DELPHI refusaient l'utilisation des API mais en donnant tout de même l'accès au risque du développeur. En fait il n'en est rien. Les fonctions API de gestion des fichiers sont très utilisées, mais pour des constructions particulières de code. L'utilisation des API WINDOWS pour la gestion de fichiers fait appel à des concepts qui dépassent ce tutorial. Je le précise simplement pour mettre en garde le débutant.

Je vais donc me borner dans cette première partie de l'article à n'utiliser que les méthodes PASCAL standard, hormis certaines API dont nous aurons tout de même besoin.
Il est indispensable d'accéder à certaines informations et gestions de fichiers que seul le système d'exploitation est capable de gérer. Les concepteurs de DELPHI ont donc adapté l'implémentation de certaines de ces fonctions des API pour que le développeur puisse les utiliser de façon transparente comme si il s'agissait de pures fonctions PASCAL natives.

Remarque importante : De nombreux objets DELPHI comme le TMemo, ou le TStringList et bien d'autres possèdent des méthodes permettant de charger ou de sauvegarder directement le contenu d'un fichier.

Exemple :

  Memo1.Lines.LoadFromFile('C:\MonTexte.txt');

C'est un automatisme apporté à l'objet qui simplifie le travail du développeur. Mais, le nombre de ces méthodes est limité, et ne nous apprend rien sur le fonctionnement et sur la gestion des fichiers.


4-2. Les principales routines de gestion de fichier en PASCAL.

   
AssignFile : Associer un nom de fichier à une variable fichier.
Rewrite : Pour créer un nouveau fichier.
Reset : Pour ouvrir un fichier existant.
Read : Pour lire le contenu d'un fichier typé.
ReadLn : Pour lire le contenu d'un fichier texte (TextFile).
Write : Pour écrire dans un fichier typé.
Append : Pour écrire dans un fichier texte en mode ajout.
BlockRead : Pour lire dans un fichier non typé.
BlockWrite : Pour écrire dans un fichier non typé.
FileSize : Pour connaitre le nombre d'enregistrements d'un fichier typé, ou la taille en octets
d'un fichier non typé. (ne fonctionne pas avec les fichiers texte)
FileClose : fermer un fichier.
Seek : Placer le pointeur de fichier à une position d'enregistrement.
DeleteFile : Supprimer un fichier existant.
RenameFile : Changer le nom d'un fichier.
FileGetAttr : Renvoit les attributs d'un fichier.


La valeur envoyée par FileGetAttr renvoie les attributs du fichier selon le type prédéfini TSearchRec.

Voici la description du type TSearchRec (documentation de l'aide de DELPHI).
type 
  TSearchRec = record
                Time : Integer;
                Size : Integer;
                Attr : Integer;
                Name : TFileName;
                ExcludeAttr : Integer;
                FindHandle : THandle;
                FindData : TWin32FindData;
               end;

Constante Valeur Description
     
faReadOnly $00000001 Fichiers en lecture seule
faHidden $00000002 Fichiers cachés
faSysFile $00000004 Fichiers système
faVolumeID $00000008 Fichiers d'identification de volume
faDirectory $00000010 Fichiers répertoire
faArchive $00000020 Fichiers archive
faAnyFile $0000003F Tout fichier


On se doute bien que la fonction FileGetAttr recherche les information dans la table d'allocation de fichier, et non dans le fichier lui-même. FileGetAttr utilise la fonction GetFileAttributes implémenté dans l'unité WINDOWS, cette une API.

Ce chapitre est sans doute un peu difficile, mais il expose des notions fondamentales sur la gestion des fichiers sous environnement WINDOWS.


5. Fichiers typés et non typés.

Un fichier typé est un fichier construit selon un type défini de données. Le type de donnée peut être un type prédéfini par le langage ou bien un type de donnée définit, pour un besoin particulier, par le développeur.

Un fichier non typé est un fichier qui n'a pas de type particulier. On pourra donc y enregistrer n'importe quoi à condition de gérer correctement les données.

Le mot réservé File permet de définir un identificateur de fichier typé ou non typé.



5-1. Déclaration d'un identifiant de fichier

Déclaration d'un fichier non typé.
 Var
    Fichier : File;
Déclaration d'un identifiant de fichier selon un type prédéfinit.
Var
  Fichier : File Of Integer;
Un type fichier prédéfinit, le type fichier texte.
Var
  Fichier : TextFile;


Eclaircissement et précision importants pour le débutant :

J'ai expliqué en chapitre -3-, que les fichiers tableurs CSV, le fichier INI et le fichier HTM ont exactement le même format qu'un fichier texte portant l'extension TXT.
Cependant il existe le type prédéfinit TIniFile. C'est une classe qui permet grâce à des automatismes de gérer assez facilement les rubriques du fichier et ce qu'elles contiennent. Il y a un excellent tutoriel dans la FAQ DELPHI du site à cette adresse : FICHIERS INI

Si vous voulez créer et utiliser un fichier CSV ou HTM, vous le déclarerez et vous l'utiliserez exactement comme un fichier texte.
NB : Vous pouvez en faire de même pour un fichier INI. En ce cas, vous devrez écrire vous-même les routines pour en extraire ou y rentrer des données, ce que fait la classe TIniFile.



5-2. Les structures dans les fichiers

Déclaration d'un identifiant de fichier selon un type définit pour un besoin.
Type
   TDonnees = Record
                 ValeurEntiere  : Integer;
                 ValeurDecimale : Double;
              End;

Var
   Fichier : File Of TDonnees;
Continuons avec un fichier typé simple. Reprenons la structure définie plus haut :

Le type de donnée "TDonnees" étant de dimension connue son écriture et sa lecture seront réduites au plus simple.
Var
  Donnees : TDonnees;
  Fichier : File Of TDonnees;

Begin
   Donnees.ValeurEntiere := 1298;
   Donnees.ValeurDecimale := 78.123;
   AssignFile(Fichier,'Nom_du_fichier');
   Rewrite(Fichier);
   Write(Fichier,Donnees);
   CloseFile(Fichier);
End;
Nous avons en une seule opération (Write), enregistré toutes les données. Quelles que soient les valeurs contenues dans les composantes de la structure, sa dimension restera la même 4 octets pour la variable "ValeurEntiere" et 8 octets pour la variable "ValeurDecimale".

La lecture sera aussi simple.
Type
    TDonnees = Record
                  ValeurEntiere  : Integer;
                  ValeurDecimale : Double;
               End;

Var
  Donnees : TDonnees;
  Fichier : File Of TDonnees;

Begin
   Donnees.ValeurEntiere := 1298;
   Donnees.ValeurDecimale := 78.123;
   AssignFile(Fichier,'Nom_du_fichier');
   Reset(Fichier);
   Read(Fichier,Donnees);
   CloseFile(Fichier);
End;

5-3. Organisation d'un fichier

Fichier à organisation séquentielle.
Fichier à organisation relative.
Fichier à organisation indexée.


Les enregistrements d'un fichier séquentiel sont écrits sur le support physique dans l'ordre physique dans lequel ils sont entrés. Il y a donc une correspondance entre l'ordre physique et l'ordre logique.

Dans un fichier à organisation relative, les enregistrements sont de taille fixe. Ces enregistrements portent un numéro relatif au début du fichier, de zéro à N. Chaque numéro représente l'emplacement sur le disque.

Dans l'organisation indexée, chaque enregistrement est identifié par un numéro (une clef) et à chaque clef est associé un numéro d'enregistrement qui renvoie à l'enregistrement correspondant.

NB : Un fichier séquentiel peut être composé d'enregistrements de taille fixe. On peut ajouter des blocs de données au fichier, en retirer, et modifier des blocs sans avoir besoin de charger tout le fichier. C'est cette méthode que nous allons utiliser dans notre didacticiel.




Exemple de définition d'un fichier a structure fixe.
Type
    // Définition de la structure d'enregistrement
   TDonnees = Record
                 Nom       : String[40];
                 PreNom    : String[40];
                 Adresse   : String[80];
                 Telephone : String[20];
                 Age       : Integer;
              End;

Var
   Donnees    : TDonnees;
   Fichier    : File Of TDonnees;
   NomFichier : String;
   Indice     : Integer;
Nous avons une vaviable "Donnees" dont l'identificateur est définit, donc sa taille est connue : 41 + 41 + 81 + 21 + 4 = 188 octets. La fonction FileSize renvoi le nombre d'enregistrements selon le type de fichier définit et non pas le nombre d'octets qu'il occupe sur le disque. Le fichier étant du type TDonnees qui fait 188 octet, si le fichier occupe 188 octets il y a 1 seul enregistrement : FileSize renverra 1.


6. Les types String et AnsiString dans les structures.

Le type AnsiString est le type de chaîne de caractère par défaut. Elle n'a pas de mémoire allouée à sa déclaration, c'est un pointeur.

"Le mot réservé string fonctionne comme un identificateur de type générique" nous dit l'aide de DELPHI.

ShortString est aussi défini comme "ancien type string". Qu'est ce donc que l'ancien type string ? A l'origine du Turbo-Pascal, les machines travaillaient en 8 puis 16 Bits. Le type string était un tableau prédéfinit de 255 caractères. Le caractère zéro (un octet) était utilisé pour indiquer la longueur de la chaîne (0 à 255). Cela a été un très grand atout sur le C, car le type string a considérablement simplifié la programmation avec des chaînes de caractères. Le type ShortString peut être redéfinit dans sa longueur. Exemple :
Var
  Chaine1 : ShortString;// Chaîne de 255 caractères
  Chaine2 : ShortString[100] ;// Chaîne de 100 caractères
Le type AnsiString est un pointeur de caractères. Donc, une variable qui n'a pas de mémoire allouée par défaut contrairement au type ShortString.
Tout ceci nous donne :
Var
   Chaine1 : Array[0..255] Of Char;// ShortString
   Chaine2 : ^Char;     // AnsiString
Donc, si nous écrivons le code suivant :
Var
   Chaine1 : Array[0..255] Of Char;
   Chaine2 : ^Char;     
   C       : Char;

begin
   C := Chaine1[8]; // récupère le huitième caractère de la chaîne
   C := Chaine2[8]; // Provoque une erreur d'exécution avec violation d'adresse.
Nous devons donc allouer de la mémoire à la variable Chaine2 pour pouvoir l'utiliser.
Var
   Chaine1 : Array[0..255] Of Char;
   Chaine2 : ^Char;     
   C       : Char;

begin
   C := Chaine1[8]; // récupère le huitième caractère de la chaîne
   GetMem(Chaine2,10); // Allouer 10 octets à la variable  
   C := Chaine2[8];
Et le type String dans tout ça ? Comme il est dit plus haut, le mot réservé String fonctionne comme identificateur de type générique. Le compilateur Pascal va compléter et/ou modifier le code au moment de la construction du programme.
Donc, reprenons :

Allouons de la mémoire à la variable Chaine2.
Var
   Chaine1 : String[80];// ShortString de 80 caractères (255 maxi)
   Chaine2 : String;    // AnsiString

begin
   C := Chaine1[8]; // récupère le huitième caractère de la chaîne
   Chaine2 := 'Je vous salut tous';
   C := Chaine2[4]; // C = 'v'
Mais, le compilateur aura traduit le code de la façon suivante :
begin
   C := Chaine1[8]; // récupère le huitième caractère de la chaîne
   GetMem(Chaine2,SizeOf('Je vous salut tous'));
   Chaine2 := 'Je vous salut tous';
   C := Chaine2[4]; // C = 'v'
Ce mécanisme fait partie des automatismes du langage Pascal de BORLAND qui a considérablement simplifié l'usage des pointeurs.


6-1. Pourquoi avoir conservé l'ancien type String et créé des automatismes sur l'usage des pointeurs ?

pour assurer une compatibilité ascendante.
pour simplifier le travail du développeur.

 Mais il y a d'autres raisons, complémentaires.

Dans le cas de travail avec des chaînes courtes, la vitesse d'exécution est très grande car le passage de valeurs à un tableau de longueur fixe est direct. L'utilisation d'un pointeur nécessite au préalable une allocation mémoire (GetMem) après avoir calculé la quantité de mémoire à réserver (SizeOf).
Il n'est pas possible de travailler avec une structure (Record) de chaînes AnsiString si elle doit être enregistrée dans un fichier. Pour être enregistrée dans un fichier, une structure doit avoir une dimension fixe et connue d'avance. Donc le type ShortString est lui seul compatible dans une structure pour un fichier.

Dans le code suivant :
Type
   TChaine = Record
                I : Integer;
                C : String;
             End;

var
  C    : TChaine;
  F    : File Of TChaine;
Le compilateur s'arrête sur F : File Of TChaine; indiquand [Erreur] Unit1.pas(24): Le type 'TChaine' nécessite une finalisation - non autorisé dans type fichier
Type
   TChaine = Record
                I : Integer;
                C : String[60];
             End;

var
  C    : TChaine;
  F    : File Of TChaine;
Même chose pour les structures de tableaux dynamiques qui sont des pointeurs.
type
   TF = Record
           Tab : Array Of Array Of Integer;
        End;

Var
   F  : File Of TF;
Provoque une erreur de compilation.


7. Continuons avec une fonction simple, copie de fichier de n'importe quel type.

Voici une routine de copie de fichier de n'importe quel type (texte ou binaire), et qu'il soit Caché, system, ou en lecture seule.

On déclare le fichier à copier comme étant non typé et on fait appel au procédures BlockRead et BlockWrite.
//---------------------------------------------------------------
Function Copie(Source,Destination : String;Attribus : Integer) : Integer;

Var
   Fd,FS    : File;
   NL,NE    : Integer;
   Buf      : Array[1..100000] Of Byte;

Begin
   Result := -1;
   // Vider éventuellement le tampon d'erreur d'entrée/Sortie
   IoResult;
   // Fixer le mode lecture en lecture seule pour éviter les
   // erreurs de lecture si le fichier est en lecture seule.
   FileMode := 0;
   // Associer le nom du fichier à la variable FD.
   Assignfile(FD,Destination);
   // Créer un fichier en mode écriture avec une taille de base
   // de 1 octet
   Rewrite(FD,1);
   // Associer le nom du fichier à la variable FS.
   AssignFile(FS,Source);
   // Ouvrir un fichier en mode lecture avec une taille de base
   // de 1 octet
   Reset(FS,1);
   Repeat
       Application.ProcessMessages;
       // Lire un paquet de données dans le fichier source
       BlockRead(FS,Buf,SizeOf(Buf),NL);
       // Ecrire dans le destinataire autant de données lues.
       BlockWrite(FD,Buf,NL,NE);
       // Si une erreur est detecté, quitter la fonction.
       If IoResult <> 0 Then Exit;
   Until (NL = 0) Or (NE <> NL);

   CloseFile(FD);
   CloseFile(FS);
   // Fixer les attributs du fichier
   Result := FileSetAttr(Destination,Attribus);
End;
//---------------------------------------------------------------
C'est ce que l'on appelle la copie en mode RAW ou bit-à-bit.
Cette méthode de copie convient pour tout type de support, disque dur, mémoire USB ou dossier réseaux, excepté les supports en lecture seule comme les CDROMs bien sur.


8. Gestion d'un fichier texte.

Un fichier texte est un ensemble d'enregistrements dont chaque groupe de caractères représentant une ligne, est terminé par un caractère 13. C'est la procédure WriteLn qui se charge d'ajouter le code 13 (retour chariot) à la fin de chaque ligne. A la lecture, la procédure ReadLn lira autant de données (d'octets) jusque à trouver un code 13. Le pointeur de fichier sera placé immédiatement après le code 13, et le prochain appel à la procédure ReadLn, la lecture se poursuivra à partir de ce caractère. La fin d'un fichier texte est quelques fois suivie d'un code "Control Z" ou 26 en décimale.

Lecture et écriture d'un fichier texte.
Procedure Lecture(Nom : String);
Var
   F   : TextFile;
   Chn : String;

Begin
   // Assigner le nom du fichier Nom à la variable F
   AssignFile(F,Nom);
   // Ouvrir le fichier en position zéro.
   Reset(F);
   // Lire le fichier tant que la fin n'est pas atteinte.
   While Not Eof(F) Do
    begin
       ReadLn(F,Chn);
       // Traitement quelconque de la chaîne de caractères Chn.
    
    end;
   // Fermeture du fichier.
   CloseFile(F);
End;

//========================================================

Var
   Tableau  : Array[1..10] Of String;

Procedure Ecriture(Nom : String);
Var
   F   : TextFile;
   Chn : String;

Begin
   // Assigner le nom du fichier Nom à la variable F
   AssignFile(F,Nom);
   // Ouvrir le fichier en position zero.
   Rewrite(F);
   // Lire le fichier tant que la fin n'est pas atteinte.
   For L := 1 To 10 Do
    Begin
       Chn := Tableau[L];
       WriteLn(F,Chn);
    end;
   // Fermeture du fichier.
   CloseFile(F);
End;
Les procédures et fonctions Seek et FileSize ne sont pas utilisables pour un fichier texte, ou plus exactement déclaré comme TextFile.
Mais nous verrons en annexe quelques trucs et astuces qui peuvent s'avérer bien pratiques.


9. Gestion d'un fichier binaire

Un fichier binaire peut être construit selon un format devant stocker n'importe quel type (ou genre) de données. Il peut contenir une image, une base de données, un tableur. Et aussi, une vidéo ou de la musique, un répertoire téléphonique ou un agenda. Il sera construit selon une structure (ou un format) précise et devant répondre à un besoin spécifique.
On comprend donc, que seule l'imagination est la limite d'une nouvelle structure, d'un nouveau fichier.



10. Mise en pratique d'un fichier binaire, création d'un agenda téléphonique.

Pour poursuivre ce didacticiel, nous allons pas à pas construire un agenda téléphonique, avec photo d'identité et un petit texte de commentaires. Nous verrons comment trier les données selon un critère particulier et comment naviguer de façon simple et rapide dans ce type de fichier.

Base de la gestion du fichier.
Type
    // Définition de la structure d'enregistrement
   TDonnees = Record
                 Nom       : String[40];
                 PreNom    : String[40];
                 Adresse   : String[80];
                 Telephone : String[20];
                 Age       : Integer;
              End;

Var
   Donnees    : TDonnees;
   Fichier    : File Of TDonnees;
   NomFichier : String;
   Indice     : Integer;

//-----------------------------------------------------------------------
Procedure Enregistre(Donnees : TDonnees; Indice : Integer);
Begin
   // Placer le pointeur de fichier à la position "Indice"
   Seek(Fichier,Indice);
   // Ecrire les 188 octets de la variable "Donnees"
   Write(Fichier,Donnees);
End;
//-----------------------------------------------------------------------
Procedure Lecture(Var Donnees : TDonnees; Indice : Integer);
Begin
   // Placer le pointeur de fichier à la position "Indice"
   Seek(Fichier,Indice);
   // Lire 188 octets coresspondants à la variable "Donnees"
   Read(Fichier,Donnees);
End;
//-----------------------------------------------------------------------
Procedure OuvreFichier(Nom : String);
Begin
   AssignFile(Fichier,Nom);
   If Not FileExists(Nom) Then Rewrite(Fichier)
   Else Reset(Fichier);
End;
//-----------------------------------------------------------------------
Procedure FermeFichier;
Begin
   CloseFile(Fichier);
End;
Nous n'avons pas besoin de plus pour gérer notre agenda téléphonique. Bien sur, nous avons besoin d'une interface utilisateur (aussi appelé Interface Homme/Machine) pour saisir et afficher les données.


10-1. Mettons en pratique le mécanisme, avec une saisie, une navigation et un enregistrement des données.

Exercice -1-




unit Exercice1;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls, Buttons, Menus, Spin;

type
  TFichePrincipale = class(TForm)
    EditNom: TEdit;
    EditPrenom: TEdit;
    EditAdresse: TEdit;
    EditTelephone: TEdit;
    EditAge: TEdit;
    Label1: TLabel;
    Label2: TLabel;
    Label3: TLabel;
    Label4: TLabel;
    Label5: TLabel;
    MainMenu1: TMainMenu;
    Fichier1: TMenuItem;
    Nouveau1: TMenuItem;
    Ouvrir1: TMenuItem;
    Quitter1: TMenuItem;
    BtPrecedent: TBitBtn;
    BtSuivant: TBitBtn;
    StaticTextIndice: TStaticText;
    Label6: TLabel;
    SpinEdit1: TSpinEdit;
    Label7: TLabel;
    BtValider: TBitBtn;
    OpenDialog1: TOpenDialog;
    SaveDialog1: TSaveDialog;
    StaticTextNbEnr: TStaticText;
    Label8: TLabel;
    BtRecherche: TButton;
    BtAjoute: TBitBtn;
    procedure Nouveau1Click(Sender: TObject);
    procedure Ouvrir1Click(Sender: TObject);
    procedure SpinEdit1KeyPress(Sender: TObject; var Key: Char);
    procedure BtRechercheClick(Sender: TObject);
    procedure BtValiderClick(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure BtAjouteClick(Sender: TObject);
    procedure BtPrecedentClick(Sender: TObject);
    procedure BtSuivantClick(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
  private
    { Déclarations privées }
  public
    { Déclarations publiques }
    Procedure ActualiseAffichage;
  end;

var
  FichePrincipale: TFichePrincipale;

implementation

{$R *.DFM}


Type
   TDonnees = Record
                 Nom       : String[40];
                 PreNom    : String[40];
                 Adresse   : String[80];
                 Telephone : String[20];
                 Age       : Integer;
              End;

Var
   Donnees        : TDonnees;
   Fichier        : File Of TDonnees;
   NomFichier     : String;
   TailleFichier,
   NbDonnees,
   Indice         : Integer;




{-------------------------------------------------------------------}
{       Indique si une chaîne de caractères représente une valeur   }
{       entière valide.                                             }
{-------------------------------------------------------------------}
Function ChaineEntierValide(Chn : String) : Boolean;
Var
  I  : Integer;
Begin
   Result:=False;
   If (Length(Chn) = 0) Or (Chn = '-') Then Exit;
   For I:=1 To Length(Chn) Do
    If Not (Chn[I] In['0'..'9']) Then Exit;
   Result:=True;
End;
//-----------------------------------------------------------------------
Procedure Enregistre(Donnees : TDonnees; Indice : Integer);
Begin
   Seek(Fichier,Indice);
   Write(Fichier,Donnees);
   // Forcer l'écriture physique du fichier (Tampon disque -> Disque)
   CloseFile(Fichier);
   Reset(Fichier);
End;
//-----------------------------------------------------------------------
Procedure Lecture(Var Donnees : TDonnees; Indice : Integer);
Begin
   // Placer le pointeur de fichier à la position correspondante.
   Seek(Fichier,Indice);
   // Lire in bloc de données
   Read(Fichier,Donnees);
End;
//-----------------------------------------------------------------------
Procedure OuvreFichier(Nom : String);
Begin
   AssignFile(Fichier,Nom);
// Si le fichier n'existe pas, le créer si non l'ouvrir
   If Not FileExists(Nom) Then Rewrite(Fichier)
   Else Reset(Fichier);
End;
//-----------------------------------------------------------------------
Procedure FermeFichier;
Begin
   CloseFile(Fichier);
End;
//-----------------------------------------------------------------------

procedure TFichePrincipale.FormCreate(Sender: TObject);
begin
   Indice :=0;
end;
//-----------------------------------------------------------------------
procedure TFichePrincipale.FormClose(Sender: TObject; var Action: TCloseAction);
begin
   FermeFichier;
end;
Nous venons d'écrire les principales routines de gestion du fichier.
Continuons avec la gestion de l'interface utilisateur.

Nous devons actualiser les affichages des données en cours à chaque modification.

//-----------------------------------------------------------------------
procedure TFichePrincipale.ActualiseAffichage;
begin
   With Donnees Do
    begin
       EditNom.Text := Nom;
       EditPrenom.Text := Prenom;
       EditAdresse.Text := Adresse;
       EditTelephone.Text := Telephone;
       EditAge.Text := IntToStr(Age);
    end;
   StaticTextNbEnr.Caption := IntToStr(NbDonnees);
   SpinEdit1.MaxValue := NbDonnees;
   StaticTextIndice.Caption := IntToStr(Indice + 1);
end;
//-----------------------------------------------------------------------
// Création d'un nouveau fichier.
//-----------------------------------------------------------------------
procedure TFichePrincipale.Nouveau1Click(Sender: TObject);
begin
   If SaveDialog1.Execute Then
    begin
       NomFichier := SaveDialog1.FileName;
       If FileExists(NomFichier) Then
        begin
           If MessageDlg('Le fichier <' + NomFichier + '> existe !' + 
                          #13#10 +
                         'Faut il le remplacer ',
                         mtConfirmation,[mbYes,mbNo],0) = mrNo Then Exit
           Else DeleteFile(NomFichier);
        end;
      OuvreFichier(NomFichier);
      NbDonnees := 0;
    end;
end;
//-----------------------------------------------------------------------
// Ouverture d'un fichier existant.
//-----------------------------------------------------------------------
procedure TFichePrincipale.Ouvrir1Click(Sender: TObject);
begin
   If OpenDialog1.Execute Then
    begin
       NomFichier := OpenDialog1.FileName;
       If Not FileExists(NomFichier) Then
        begin
           If MessageDlg('Le fichier <' + NomFichier + '> n''existe pas !'
                          + #13#10 +
                         'Faut il le créer ?',
                         mtConfirmation,[mbYes,mbNo],0) = mrNo Then Exit;
        end;
       OuvreFichier(NomFichier);

       // FileSize renvoi le nombre d'enregistrements selon son type
       // définit et non pas le nombre d'octets qu'il occupe sur le disque.
       // Le fichier étant du type TDonnees qui fait 184 octet, si le 
       // fichier occupe 184 octets il y a 1 seul enregistrement.
       TailleFichier := FileSize(Fichier);
       If  TailleFichier >= 1 Then
        begin
           NbDonnees := TailleFichier;
           Lecture(Donnees,0);
           SpinEdit1.Value := NbDonnees;
        end;
       ActualiseAffichage;
    end;
end;
//-----------------------------------------------------------------------
// Définition de l'indice de recherché.
//-----------------------------------------------------------------------
procedure TFichePrincipale.SpinEdit1KeyPress(Sender: TObject; var Key: Char);
begin
   // Limiter la saisie aux chiffres. #8 est le code de la touche 
   // d'effacement
   If Not (Key In['0'..'9',#8]) Then Key := #0;
end;
//-----------------------------------------------------------------------
// Rechercher les données d'un élément selon son indice dans le fichier.
//-----------------------------------------------------------------------
procedure TFichePrincipale.BtRechercheClick(Sender: TObject);
Var
   Chn    : String;

begin
   Chn := SpinEdit1.Text;
   If ChaineEntierValide(Chn) Then
    begin
       Indice := SpinEdit1.Value - 1;
       If (Indice <= NbDonnees)  Then
        begin
           Lecture(Donnees,Indice);
           ActualiseAffichage;
        end;
    end;
end;
//-----------------------------------------------------------------------
// Valider les modifications d'un élément de l'agenda.
//-----------------------------------------------------------------------
procedure TFichePrincipale.BtValiderClick(Sender: TObject);
begin
   With Donnees Do
    begin
       Adresse := EditAdresse.Text;
       Nom := EditNom.Text;
       PreNom := EditPrenom.Text;
       Telephone := EditTelephone.Text;
       Age := StrToInt(EditAge.Text);
    end;
   Enregistre(Donnees,Indice);
end;


Nous devons pouvoir ajouter un élément à l'agenda. Cet élément sera écrit en fin de fichier.

//-----------------------------------------------------------------------
// Ajouter un élément à l'agenda.
//-----------------------------------------------------------------------
procedure TFichePrincipale.BtAjouteClick(Sender: TObject);
begin
   With Donnees Do
    begin
       Adresse := EditAdresse.Text;
       Nom := EditNom.Text;
       PreNom := EditPrenom.Text;
       Telephone := EditTelephone.Text;
       Age := StrToInt(EditAge.Text);
    end;
   Enregistre(Donnees,TailleFichier);
   TailleFichier := FileSize(Fichier);
   NbDonnees := TailleFichier;
   Indice:=NbDonnees - 1;
   SpinEdit1.Value := NbDonnees;
   ActualiseAffichage;
end;


Ajoutons deux boutons pour naviguer dans l'agenda.

//-----------------------------------------------------------------------
// Lire l'enregistrement précédent dans le fichier.
//-----------------------------------------------------------------------
procedure TFichePrincipale.BtPrecedentClick(Sender: TObject);
begin
   If  NbDonnees >= 1 Then
    begin
       If Indice > 0 Then
        begin
           Dec(Indice);
           Lecture(Donnees,Indice);
        end;
    end;
   ActualiseAffichage;
end;
//-----------------------------------------------------------------------
// Lire l'enregistrement suivant dans le fichier.
//-----------------------------------------------------------------------
procedure TFichePrincipale.BtSuivantClick(Sender: TObject);
begin
   If  NbDonnees >= 1 Then
    begin
       If Indice < NbDonnees - 1 Then
        begin
           Inc(Indice);
           Lecture(Donnees,Indice);
        end;
    end;
   ActualiseAffichage;
end;
//-----------------------------------------------------------------------

end.

10-2. Comment supprimer un enregistrement ?

La solution est de passer par un fichier temporaire dans lequel nous copierons tous les éléments excepté celui que nous voulons supprimer.

Exercice 2.




Ajoutons une variable pour le fichier temporaire.
implementation

{$R *.DFM}


Type
   TDonnees = Record
                 Nom       : String[40];
                 PreNom    : String[40];
                 Adresse   : String[80];
                 Telephone : String[20];
                 Age       : Integer;
              End;

Var
   Donnees        : TDonnees;
   FichierBak,     // Identificateur du fichier temporaire
   Fichier        : File Of TDonnees;
   NomFichier     : String;
   TailleFichier,
   NbDonnees,
   Indice         : Integer;


Ajoutons la procédure de suppression.

//-----------------------------------------------------------------------
procedure TFichePrincipale.BtSupprimeClick(Sender: TObject);
Var
   I  : Integer;

begin
   // Création d'un fichier temporaire
   AssignFile(FichierBak,'FichierTemp');
   If FileExists('FichierTemp') Then DeleteFile('FichierTemp');
   Rewrite(FichierBak);

   // Copier tous les enregistrements du fichier d'origine à
   // l'exception de l'enregistrement à supprimer
   For I := 0 To NbDonnees - 1 Do
    begin
       If I <> Indice Then
        begin
           Seek(Fichier,I);
           Read(Fichier,Donnees);
           Write(FichierBak,Donnees);
        end;
    end;
   Dec(NbDonnees);

   // Fermer les fichier
   CloseFile(FichierBak);
   CloseFile(Fichier);

   // Supprimer le fichier d'origine
   DeleteFile(NomFichier);

   // Renomer le fichier temporaire avec le nom d'origine
   RenameFile('FichierTemp',NomFichier);

   // Actualliser
   OuvreFichier(NomFichier);
   If NbDonnees > 0 Then
    If Indice > 0 Then
     begin
        Dec(Indice);
        Lecture(Donnees,Indice);
     end;
   ActualiseAffichage;
end;

end.
Ce petit programme peut bien sur être grandement amélioré et sécurisé. Nous allons aborder maintenant la notion de flux.


11. Qu'est ce qu'un flux ?

Un flux est un paquet de données transféré entre un fichier et un programme, et inversement. Le fichier sera dirigé vers un disque, mais il peut aussi être dirigé vers un autre support comme la mémoire vive. On parle alors généralement de fichier virtuel. On parle aussi d'enregistrement au fil de l'eau. C'est-à-dire que les données d'un calcul par exemple, sont enregistrées au fur et à mesure de la sortie des résultats.

En anglais, le mot "stream" signifie cours d'au ou ruisseau. Le mot "Stream" a donc été choisi pour désigner un flux.

Nous disposons avec DELPHI, d'une classe de base représentant un flux, la classe "TStream". Mais cette classe ne doit pas être instanciée (Voir l'aide de DELPHI). Nous disposons de plusieurs descendants de la classe TStream.

TFileStream (pour l'utilisation de fichiers).
TStringStream (pour la manipulation de chaînes en mémoire).
TMemoryStream (pour l'utilisation d'un tampon mémoire).
TBlobStream (pour l'utilisation de champs BLOB).
TWinSocketStream (pour la lecture et l'écriture sur une connexion socket).
TOleStream (pour l'utilisation d'une interface COM en lecture et écriture).



12. Reprenons notre agenda téléphonique et ajoutons une photo en face de chaque membre.

Nous-nous trouvons là, devant le problème évoqué aux chapitres -5- et -6-. La variable devant contenir l'image représentant la photo, doit avoir une dimension connue et fixée à l'avance si elle fait partie d'une structure devant être enregistrée dans un fichier. La seule solution est de définir un tableau de dimension fixe aussi grand que le fichier contenant l'image d'origine.

Nous pourrons alors, transférer directement les données de l'image BITMAP dans ce tableau d'octets.

Cette solution nous limite à des formats, types et images de structure identiques, ou, ne devant pas dépasser une dimension prévue à l'avance. Vous trouverez en chapitre -22- "Le fichier BitMap démystifié". J'y expose la structure d'un fichier BITMAP.

Les photos ne devront pas dépasser un format fixé à l'avance. Fixons nous à 150 x 150 pixels.

La seule façon de stoker l'image dans la structure de fichier, est d'utiliser un tableau définit avec une dimension suffisante. Si l'image est en 16 millions de pixels, elle occupera (en format 32 bit) 90000 octets plus l'entête définissant l'image soit un total de 90054 octets. Nous allons devoir passer par un flux pour charger et stocker l'image dans la structure.

Exercice 3.




Modification de la structure.

Type
   TDonnees = Record
                 Nom         : String[40];
                 PreNom      : String[40];
                 Adresse     : String[80];
                 Telephone   : String[20];
                 Age         : Integer;
                 Photo       : Array[0..90054] Of Byte;
                 TaillePhoto : Integer;
              End;


Procédure pour transférer une image BitMap dans le tableau, et inversement pour restituer l'image dans le BitMap depuis le tableau.

//-----------------------------------------------------------------------
procedure TFichePrincipale.TransfertPhotoDansStructure;
Var
   Flux : TMemoryStream;
   C, T : Integer;
   Buff : ^Byte;

begin
   Flux := TMemoryStream.Create;
   // Transfert de la photo dans le flux
   Image1.Picture.Bitmap.SaveToStream(Flux);
   // Connaitre la taille en octets du flux
   T := Flux.Size;
      // Attribuer un espace mémoire au buffer
   GetMem(Buff,T);
   // Placer le lecteur du flux en position zéro.
   Flux.Position :=0;
   // Transfert des données de la photo dans le tableau
   Flux.Read(Buff^,T);
   Move(Buff^,Donnees.Photo,T);
   // Memorisation de la taille du flux
   Donnees.TaillePhoto := T;
   Flux.Free;
end;
//-----------------------------------------------------------------------
procedure TFichePrincipale.TransfertPhotoDepuisStructure;
Var
   Flux : TMemoryStream;
   T    : Integer;
   Buff : ^Byte;

begin
   Flux := TMemoryStream.Create;
   // A ce stade, le flux n'a pas d'espace réservé. Il faut donc lui
   // atribuer un espace mémoire correspondant à la taille du bitmap.
   T:= Donnees.TaillePhoto;
   Flux.SetSize(T);
   // Transfert des données de la photo depuis le tableau, dans le flux
   GetMem(Buff,T);
   Move(Donnees.Photo,Buff^,T);
   // Replacer le pointeur de flux à la position 0
   Flux.Seek(0,soFromBeginning);
   Flux.Position :=0;
   Flux.Write(Buff^,T);
   // Replacer le pointeur de flux à la position 0
   Flux.Seek(0,soFromBeginning);
   // Transfert du flux dans le bitmap
   Image1.Picture.BitMap.LoadFromStream(Flux);
   Image1.Refresh;
   Flux.Free;
end;
//-----------------------------------------------------------------------
// Ouverture du fichier BitMap pour la photo d'identité
procedure TFichePrincipale.BtPhotoClick(Sender: TObject);
Var
   Nom  : String;

begin
   If OpenPictureDialog1.Execute Then
    begin
       Nom := OpenPictureDialog1.FileName;
       // Attention, il n'y a pas de sécurité pour la dimension de l'image
       Image1.Picture.LoadFromFile(Nom);
    end;
end;
//-----------------------------------------------------------------------
Nous verrons aux chapitres -15- et -16- comment sécuriser le chargement d'une image (selon ses dimensions), et comment redimensionner un bitmap au bon format.


13. Continuons notre didacticiel avec l'ajout d'un commentaire pour chaque élément.

Le texte sera limité à 255 caractères pour rester dans même logique d'utilisation de la structure.

Exercice 4.




Complément de la structure avec une chaine de caractères qui contiendra le commentaire.

Type
   TDonnees = Record
                 Nom         : String[40];
                 PreNom      : String[40];
                 Adresse     : String[80];
                 Telephone   : String[20];
                 Age         : Integer;
                 Photo       : Array[0..90053] Of Byte;
                 TaillePhoto : Integer;
                 Commentaire : ShortString; // 255 caractères maxi
              End;
Complétons les procédures pour l'utilisation du commentaire.
//-----------------------------------------------------------------------
procedure TFichePrincipale.ActualiseAffichage;
Var
  Chn : String;

begin
   With Donnees Do
    begin
       EditNom.Text := Nom;
       EditPrenom.Text := Prenom;
       EditAdresse.Text := Adresse;
       EditTelephone.Text := Telephone;
       EditAge.Text := IntToStr(Age);
       TransfertPhotoDepuisStructure;
       Chn := Commentaire;
    end;
   // Vérification si la chaîne commentaire contient un texte et
   // ajout dans le mémo.
   If Chn <> '' Then Memo1.Lines.SetText(PChar(Chn));

   StaticTextNbEnr.Caption := IntToStr(NbDonnees);
   SpinEdit1.MaxValue := NbDonnees;
   StaticTextIndice.Caption := IntToStr(Indice + 1);
end;
//-----------------------------------------------------------------------
procedure TFichePrincipale.BtValiderClick(Sender: TObject);
Var
   Chn  : String;
   L, I : Integer;
begin
   L := Memo1.Lines.Count;
   Chn := '';
// Si le Memo contient un texte, ajouter ce texte dans l'enregistrement.
   If L > 0 Then
    Begin
       // Copier le contenu du memo dans la chaine de caractères.
       For I:=0 To L-1 Do Chn := Chn + Memo1.Lines[I];
    end;


   With Donnees Do
    begin
       Adresse := EditAdresse.Text;
       Nom := EditNom.Text;
       PreNom := EditPrenom.Text;
       Telephone := EditTelephone.Text;
       Age := StrToInt(EditAge.Text);
       TransfertPhotoDansStructure;
       Commentaire := Chn;
    end;
   // Enregistrer les données dans le fichier.
   Enregistre(Donnees,Indice);
end;
//-----------------------------------------------------------------id="XV"------

14. Tri des données.

Il nous reste maintenant à trier notre agenda par noms.

Là encore, nous allons devoir réécrire le fichier dans un ordre croissant selon les noms des éléments. C'est grâce à la puissance du compilateur PASCAL de BORLAND que nous pouvons écrire un algorithme simple de tri.

Si nous faisons une comparaison de grandeur entre deux chaînes de caractères, ce n'est pas la longueur de la chaîne que nous obtenons, mais la valeur numérique de la somme des caractères.

La chaîne "ABC" est plus petite que la chaîne "DEF".
Le test si "ABC" est plus petit que "DEF" est traduit par :
 Si (65 + 66 +67) est plus petit que (68 + 69 + 70) .
Une chaîne de caractères est une suite d'octets dont chaque valeur peut aller de 0 à 255.

C'est donc grâce à un test numérique que nous pouvons trier un ensemble de chaînes de caractères.

Principe :

 Chn1 := TableNoms[L].Nom;
 Chn2 := TableNoms[L + 1].Nom;
 If Chn1 > Chn2 Then Valide := True;

Nous allons tout d'abord charger l'ensemble des variables "Nom" de tous les enregistrements, ainsi que la position de l'enregistrement correspondant dans un tableau.

Créons une nouvelle structure pour mémoriser les noms et leurs indices dans le fichier, et un tableau dynamique dont la taille sera défini selon le nombre d'enregistrement de l'agenda.

Exercice 5.
Type

TTri = Record
              Index   : Integer;
              Nom     : String[40];
           End;


Var
   TableNoms      : Array Of TTri;
Procédure de chargement des variables "Nom" ainsi que leur position correspondante dans le fichier.
//-----------------------------------------------------------------------
Procedure ChargeNomsAgenda;
Var
  I  : Integer;

Begin