LES FICHIERS DESCRIPTIONS ET UTILISATION Première partie : les bases

Philippe GORMAND

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 de 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 commentaires.

- Titre : LES FICHIERS DESCRIPTIONS ET UTILISATION Première partie : les bases

- Auteur : Philippe GORMAND

- Parution : 13 octobre 2006

- Mise à jour : 30 décembre 2019

- Licence : Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2006 Philippe GORMAND. 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

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

- II. Pas de fichier sans un support matériel

- III. Les différents formats de fichiers et leur extension

- III-A. Fichier texte

- III-B. Fichier binaire

- III-C. Les fichiers de type texte ne portant pas l'extension « .txt »

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

- III-C-2. Le fichier INI

- III-C-3. Le fichier HTML

- III-D. Qu'est-ce que le « Rich text file » ou « fichier texte enrichit » portant l'extension « .rtf » ?

- III-E. Les fichiers avec entête et les fichiers sans entête

- III-F. Cas particulier, le fichier avec l'extension « .RAW »

- IV. Les routines de gestions de fichiers

- IV-A. Création d'un fichier : quelle méthode utiliser ?

- IV-B. Les principales routines de gestion de fichiers en PASCAL.

- V. Fichiers typés et non typés

- V-A. Déclaration d'un identifiant de fichier

- V-B. Les structures dans les fichiers

- V-C. Organisation d'un fichier

- VI. Les types String et AnsiString dans les structures

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

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

- VIII. Gestion d'un fichier texte

- IX. Gestion d'un fichier binaire

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

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

- X-B. Comment supprimer un enregistrement ?

- XI. Qu'est-ce qu'un flux ?

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

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

- XIV. Tri des données

- XV. Dimensionnement de l'image au format 150 x 150

- XVI. Ajoutons une procédure de chargement des images JPEG

- XVII. Modification de la procédure de chargement de l'image pour plus de sécurité

- XVIII. Conclusion des exercices

- XIX. Gestion des fichiers et sécurité

- XX. Complément : quelques fonctions et procédures utiles

- XXI. Trucs et astuces

- XXI-A. Connaître la taille en octets de n'importe quel fichier

- XXI-B. Imposer le dossier par défaut de notre agenda au démarrage du programme

- XXI-C. Empêcher l'effacement accidentel d'un fichier

- XXI-D. Si vous voulez changer la date et l'heure de création du fichier

- XXII. Le fichier Bitmap démystifié

- XXII-A. Structure d'un fichier Bitmap

- XXII-B. Exemple d'une palette de conversion en 256 couleurs

- XXII-C. Programme permettant de connaître les caractéristiques d'une image Bitmap sans charger tout le fichier

- XXIII. Liste des codes d'erreurs Entrées/Sorties DELPHI et WINDOWS

- XXIV. Sources des exercices

- XXV. Liens divers et bibliographie

- XXVI. Que réserve la seconde partie du tutoriel ?

- XXVII. L'article complet au format PDF

I - 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 ont été conservés. 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 CD-ROM).
Bien sûr, la notion de fichier a énormément évolué. 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.

II - 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émoires 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 CD-ROM 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 CD-ROM ou DVD. C'est un disque optique ne contenant qu'une seule piste en spirale. Un CD-ROM 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 parlerai 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'écriture 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 ?

Trois parties les composent. Le secteur de Boot, la table d'allocation de fichiers 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 fichiers 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 (système, 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 CD-ROM, c'est un fichier en début de piste qui tient lieu de table d'allocation de fichiers.

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 s'il existe.

À 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.

III - Les différents formats de fichiers et leur extension

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 texte et les fichiers binaires. On parle de format, de fichier typé et de fichier non typé. Nous verrons progressivement le 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 parmi 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és « binaire », car leur utilisation nécessitait 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.

III-A - 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 chaine. À 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 chaine 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 chaine réceptrice.

III-B - Fichier binaire

On appelle 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, etc.

III-C - 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 fichiers ayant la même structure (du point vue informatique) que le fichier texte, mais utilisés de façon particulière. Les fichiers CSV, INI et HTML.

III-C-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. À la lecture, il suffit de compter le nombre de points-virgules dans la chaine 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

III-C-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 tutoriels sur le sujet et de bons exemples dans la FAQ DELPHI du site.

III-C-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és de balises (des mots réservés au langage HTML) qui les feront apparaître avec une taille, un style et une couleur définis 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.

III-D - 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
saurais 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.

III-E - 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étafichier (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;

III-F - 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éfini 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.

IV - 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'ont prévu.

IV-A - 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 fichiers (sur le disque) les informations qui permettront son 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. N. B. 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ées par DELPHI. On peut donc les utiliser (a 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 choisi 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 tutoriel. 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 s’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.

IV-B - Les principales routines de gestion de fichiers 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 connaître 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

Renvoie 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 informations dans la table d'allocation de fichiers, et non dans le fichier lui-même. FileGetAttr utilise la fonction GetFileAttributes implémentée dans l'unité WINDOWS, c'est une API.

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

V - 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ées peut être un type prédéfini par le langage ou bien un type de données défini, 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é.

V-A - 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éfini.

Var
Fichier : File Of Integer;

Un type fichier prédéfini, le type fichier texte.

Var
Fichier : TextFile;

Éclaircissement 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éfini 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.
N. B. 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.

V-B - Les structures dans les fichiers

Déclaration d'un identifiant de fichier selon un type défini 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;

V-C - 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.

N. B. 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 à 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 variable « Donnees » dont l'identificateur est défini, donc sa taille est connue : 41 + 41 + 81 + 21 + 4 = 188 octets. La fonction FileSize renvoie le nombre d'enregistrements selon le type de fichier défini et non pas le nombre d'octets qu'il occupe sur le disque. Le fichier étant du type TDonnees qui fait 188 octets, si le fichier occupe 188 octets il y a 1 seul enregistrement : FileSize renverra 1.

VI - Les types String et AnsiString dans les structures

Le type AnsiString est le type de chaine de caractères 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 ? À l'origine du Turbo-Pascal, les machines travaillaient en 8 puis 16 bits. Le type string était un tableau prédéfini de 255 caractères. Le caractère zéro (un octet) était utilisé pour indiquer la longueur de la chaine (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 chaines de caractères. Le type ShortString peut être redéfini dans sa longueur. Exemple :

Var
Chaine1 : ShortString;// chaine de 255 caractères
Chaine2 : ShortString[100] ;// chaine 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 chaine
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 chaine
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 chaine
Chaine2 := 'Je vous salue 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 chaine
GetMem(Chaine2,SizeOf('Je vous salue tous'));
Chaine2 := 'Je vous salue 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.

VI-A - Pourquoi avoir conservé l'ancien type String et créé des automatismes sur l'usage des pointeurs ?

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

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

3° Dans le cas de travail avec des chaines 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).
4° Il n'est pas possible de travailler avec une structure (Record) de chaines 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; indiquant [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.

VII - 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é, système, ou en lecture seule.

On déclare le fichier à copier comme étant non typé et on fait appel aux 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'erreurs 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);
// Écrire dans le destinataire autant de données lues.
BlockWrite(FD,Buf,NL,NE);
// Si une erreur est détectée, 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éseau, excepté les supports en lecture seule comme les CD-ROM bien sûr.

VIII - 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. À la lecture, la procédure ReadLn lira autant de données (d'octets) jusqu’à 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 chaine 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 zéro.
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.

IX - 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.

X - 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);
// Écrire 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 correspondants à 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 sûr, nous avons besoin d'une interface utilisateur (aussi appelé Interface Homme/Machine) pour saisir et afficher les données.

X-A - 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 chaine 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 un 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 sinon 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 renvoie le nombre d'enregistrements selon son type
// défini et non pas le nombre d'octets qu'il occupe sur le disque.
// Le fichier étant du type TDonnees qui fait 184 octets, 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 recherche.
//-----------------------------------------------------------------------
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.

X-B - 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);

// Renommer 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 sûr être grandement amélioré et sécurisé. Nous allons aborder maintenant la notion de flux.

XI - 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'eau 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 chaines 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).

XII - 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 structures identiques, ou ne devant pas dépasser une dimension prévue. 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éfini avec une dimension suffisante. Si l'image est en 16 millions de pixels, elle occupera (en format 32 bits) 90 000 octets plus l'entête définissant l'image soit un total de 90 054 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);
// connaître 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;
// À ce stade, le flux n'a pas d'espace réservé. Il faut donc lui
// attribuer 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.

XIII - 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 chaine 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"------

XIV - 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 chaines de caractères, ce n'est pas la longueur de la chaine que nous obtenons, mais la valeur numérique de la somme des caractères.

La chaine « ABC » est plus petite que la chaine « 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 chaine 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 chaines 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éfinie selon le nombre d'enregistrements 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
// Définition de la taille du tableau selon le nombre d'enregistrements
SetLength(TableNoms,NbDonnees);
// Plaçons le pointeur de fichier en début.
Seek(Fichier,0);
// Lecture de tous les enregistrements.
For I := 0 To NbDonnees - 1 Do
begin
Read(Fichier,Donnees);
TableNoms[I].Index := I;
TableNoms[I].Nom := Donnees.Nom;
end;
End;
//-----------------------------------------------------------------------

Maintenant, nous devons trier le tableau par ordre de grandeur de la variable « Nom ».

//-----------------------------------------------------------------------
Procedure TrierParNoms;
Var
L, C, IdTemp  : Integer;
ChnTemp,
Chn1, Chn2    : String;

Begin
If NbDonnees < 2 Then Exit;
For L := 0 To NbDonnees - 2 Do
For C := L + 1 To NbDonnees - 1 Do
begin
Chn1 := TableNoms[L].Nom;
Chn2 := TableNoms[C].Nom;
If Chn1 > Chn2 Then
begin
ChnTemp := TableNoms[L].Nom;
IdTemp := TableNoms[L].Index;
TableNoms[L].Nom := TableNoms[C].Nom;
TableNoms[L].Index := TableNoms[C].Index;
TableNoms[C].Nom := ChnTemp;
TableNoms[C].Index := IdTemp;
end;
end;
//-----------------------------------------------------------------------

Nous avons trié le tableau, il va falloir maintenant réécrire le fichier dans le bon ordre. Le tableau « TableNoms » est constitué d'un ensemble de mots triés par ordre numérique, et un indice en face de chaque mot (« Nom ») correspondant à sa position dans le fichier. La procédure est très simple : lire le premier élément du tableau TableNoms et récupérer la valeur de la variable Index. Placer ensuite le pointeur de fichier de l'agenda à la position Index. Lire le bloc de données correspondant et écrire ce bloc de données dans un fichier temporaire. Lire l'élément suivant, et ainsi de suite.

// Réécriture du fichier
// 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
// dans le fichier temporaire.
For L := 0 To NbDonnees - 1 Do
Begin
// Lecture de la position de l'enregistrement correspondent à "Nom"
Indice := TableNoms[L].Index;
// Placer le pointeur de fichier à la position Indice
Seek(Fichier,Indice);
// Lire le bloc de données.
Read(Fichier,Donnees);
// Écrire le bloc de données dans le fichier temporaire à
// la suite du précédent.
Write(FichierBak,Donnees);
end;

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

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

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

End;

Réorganisons la fiche pour une meilleure présentation.

Il suffit de cliquer sur un nom dans la liste pour charger les données concernant un individu.

Voici notre agenda terminé. Vous pourrez aisément constater qu'en le modifiant sans aucune difficulté, il peut servir de catalogue d'une bibliothèque, de suivi des avancées d'une classe d'élèves ou de répertoire des membres d'un club.

XV - Dimensionnement de l'image au format 150 x 150

Il serait confortable de pouvoir charger des images à des formats d'origine plus grands que ceux imposés par la structure du fichier. Pour cela, nous devons redimensionner l'image pour qu'elle tienne dans un espace de 150 x 150 pixels.

//------------------------------------------------------------
// Redimensionner l'image au format 150 x 150
//------------------------------------------------------------
Procedure TFichePrincipale.RedimensionneImage;
Var
BMP     : TBitMap;
L, H, D : Double;
X, Y    : Integer;

Begin
L := Image1.Picture.BitMap.Width;
H := Image1.Picture.BitMap.Height;
BMP := TBitMap.Create;
// Vérifier les dimensions de l'image
If (L > 150) Or (H > 150) Then
Begin
// Calculer le facteur de division
If L > H Then D := L / 150
Else D := H / 150;
// Calculer les nouvelles dimensions du Bitmap.
X:=Round(L / D) ;
Y:=Round(H / D);
end
Else
begin
BMP.Free;
Exit;
end;
// Attribuer les dimensions correctes au Bitmap tampon.
BMP.Width := X;
BMP.Height := Y;
// Copier l'image en la redimensionnant dans le tampon Bitmap
BMP.Canvas.StretchDraw(Rect(0,0,X,Y),Image1.Picture.BitMap);
// Remplacer l'image redimensionnée.
Image1.Picture.BitMap.Assign(BMP);id="XVII"
BMP.Free;
End;

XVI - Ajoutons une procédure de chargement des images JPEG

Cela nous apportera plus de confort. La gestion JPEG étant en standard dans DELPHI, la chose n'est pas difficile.

//------------------------------------------------------------
// Charge une image au forma JPEG et décompresse en Bitmap
//------------------------------------------------------------
Procedure TFichePrincipale.ChargeImageJPEG(Nom : String;Image : TImage);
Var
ImageJPEG   : TJPEGImage;
ImageBmp    : TBitMap;

Begin
ImageJPEG:=TJPEGImage.Create;
Try
ImageJPEG.LoadFromFile(Nom);
ImageBmp:=TBitMap.Create;
Try
ImageBmp.Assign(ImageJPEG);
Image.Picture.BitMap.Assign(ImageBmp);
finally
ImageBmp.Free;
end;
Finally
ImageJPEG.Free;
end;
End;
//-------------------------------------------id="XVIII"----------------------------

XVII - Modification de la procédure de chargement de l'image pour plus de sécurité

Assurons-nous du type de fichier selon son extension (bmp ou jpg) et redimensionnons l'image si celle-ci dépasse le format imposé.

procedure TFichePrincipale.BtPhotoClick(Sender: TObject);
Var
Nom  : String;

begin
If OpenPictureDialog1.Execute Then
begin
Nom := OpenPictureDialog1.FileName;
If UpperCase(ExtractFileExt(Nom)) = '.JPG' Then
ChargeImageJPEG(Nom,Image1)
Else Image1.Picture.LoadFromFile(Nom);
If (Image1.Picture.Width > 150) Or
(Image1.Picture.Height > 150) Then
id="XIX"     RedimensionneImage;
end;
end;

XVIII - Conclusion des exercices

Nous avons vu l'essentiel des routines de gestion de fichiers PASCAL, la technique d'utilisation d'une structure pour enregistrer des données dans un fichier et une astuce pour y inclure un BLOB (ici un fichier bitmap). L'aspect de la sécurité a été abordé, mais nous allons quelque peu l'approfondir dans le chapitre suivant.

Je vais poursuivre en parlant de quelques routines très intéressantes et de quelques trucs et astuces concernant l'utilisation des fichiers. Il y a également quelques API WINDOWS dont il est bon de parler.

XIX - Gestion des fichiers et sécurité

Que sont les opérations d'entrées/sorties ? Ce sont les opérations de lecture/écriture de fichiers. Pas seulement les procédures Read, BlockRead, Write, BlockWrite, etc., mais également les fonctions et procédures comme RenameFile, Reset et autre. Si aucune précaution n'a été mise en place, il y aura plantage du programme lors d'une erreur. Il existe une directive de compilation qui permet d'intercepter les erreurs d'opération E/S à l'aide d'une fonction. La directive {$I-} et la fonction IOResult.

Je donne ici un court exemple, vous trouverez une explication détaillée dans l'aide de DELPHI.

//-----------------------------------------------------------------------
{$I-}
Var
F      : File;
Retour : Integer;

Begin
AssignFile(F,Nom);
Reset(F);
Retour := IOResul;
If Retour > 0 Then
MessageDlg('Erreur d''entrée/sortie N° ' + IntTostr(Retour),
mtErro,[mbOK],0);
End;
//-----------------------------------------------------------------------

Pour éviter le déclenchement (et la gestion) d'erreur lors d'une tentative d'ouverture d'un fichier, utilisez la fonction FileExits pour vérifier que le fichier existe bien (on peut faire une faute dans le nom).

La fonction DiskFree renvoie l'espace disponible sur le disque de destination.

XX - Complément : quelques fonctions et procédures utiles

Je ne peux pas énumérer toutes les fonctions et procédures qui ont trait à la gestion des fichiers. Je vous engage donc à consulter l'aide sur l'Unité System, Catégorie : routines d'entrées/sorties.

Pour notre Agenda, vous pouvez avoir besoin de connaître la date de dernière modification. Utilisation de la fonction FileAge :

//-----------------------------------------------------------------------
Var
Age              : Integer;
DateHeureFichier : TDateTime;

Begin
Age := FileAge(NomFichier);
DateHeureFichier := FileDateToDateTime(Age);

//-----------------------------------------------------------------------

DirectoryExists

permet de créer un nouveau dossier en une seule opération.

ForceDirectories

pour créer un nouveau fichier.

ChDir

change le répertoire en cours.

CreateDir

crée un nouveau répertoire. À la différence de ForceDirectories, ChDir ne crée qu'une seule occurrence.

GetDir

retourne le nom du répertoire en cours.

ExtractFileName

renvoie le nom seul, d'un fichier s'il est précédé du chemin.

Append

pour écrire dans un fichier texte en mode ajout.

ExtractFilePath

renvoie le chemin seul, d'un fichier s'il est précédé du chemin.

XXI - Trucs et astuces

XXI-A - Connaître la taille en octets de n'importe quel fichier

Comme il est dit dans l'aide de DELPHI, la fonction FileSize n’est pas applicable avec un fichier de type texte (type prédéfini TextFile).

Voici une astuce permettant de connaître la taille réelle en octets de n'importe quel fichier. Il suffit d'utiliser la fonction FileSize en déclarant la variable fichier sans type.

//-----------------------------------------------------------------------
Function TailleFichier(Nom : String) : Integer;
Var
F   : File;

Begin
AssignFile(F,Nom);
Reset(F,1);
Result := FileSize(F);
CloseFile(F);
End;
//-----------------------------------------------------------------------

XXI-B - Imposer le dossier par défaut de notre agenda au démarrage du programme

Vous pouvez faire en sorte que lors de l'affichage de la boite de dialogue d'ouverture d'un fichier (notre agenda), le chemin par défaut soit toujours le même. Supposons que nous ayons créé le dossier devant contenir l'agenda se trouve dans le dossier principal du programme.

Var
Principal,
Dossier     : String;

//Dans la procédure FormCreate de l'application, entrez le code suivant :

Principal := ExtractFileExt(ParamStr(0));
If Principal[Length(Principal)] <> '\' Then Principal := Principal + '\';
Dossier := 'Agenda';
OpenDialog1.InitialDir := Principal + Dossier;

XXI-C - Empêcher l'effacement accidentel d'un fichier

La solution est de paramétrer l'attribut du fichier en lecture seule à la fermeture du programme, et de rétablir l'autorisation d'écriture avant l'ouverture du fichier.

Function  ChangeLectureSeule(Nom : String;LectureSeule : Boolean) : Boolean;
Var
R,Attr,AttrC : Integer;

Begin
Attr:=FileGetAttr(Nom);
If Attr = 0 Then Attr:=faArchive;
AttrC:=0;
If (Attr And faHidden = faHidden) Then AttrC:=faHidden;
If (Attr And faSysFile = faSysFile) Then Attrc:=AttrC + faSysFile;
If (Attr And faVolumeID = faVolumeID) Then Attrc:=AttrC + faVolumeID;
If (Attr And faDirectory = faDirectory) Then Attrc:=AttrC + faDirectory;
If (Attr And faArchive = faArchive) Then Attrc:=AttrC + faArchive;
If (Attr And faAnyFile = faAnyfile) Then Attrc:=AttrC + faAnyfile;

If LectureSeule Then AttrC:=AttrC + faReadOnly;
R:=FileSetAttr(Nom,AttrC);

Result:=R = 0;
End;

XXI-D - Si vous voulez changer la date et l'heure de création du fichier

Function  ChangeDateHeureFichier(Nom : String;DateHeure : TDateTime) : Boolean;
Var
H   : Integer;
DH  : Integer;

Begin
DH:=DateTimeToFileDate(DateHeure);
H:=FileOpen(Nom,faAnyFile);
If H = -1 Then Result:=False
Else
begin
FileSetDate(H,DH);
FileClose(Hid="");
Result:=True;
end;
End;

XXII - Le fichier Bitmap démystifié

Je vais en profiter pour démystifier le fichier « Bitmap » (.bmp), et montrer comment on peut charger son entête pour connaître ses caractéristiques.

XXII-A - Structure d'un fichier Bitmap

Entête de fichier.

Le fichier Bitmap est une suite d'octets représentant les valeurs rouge, vert et bleu de chaque pixel, précédé d'un entête définissant les caractéristiques de l'image. Mais, l'organisation des données est différente selon le type de l'image, du monochrome à 16 millions de couleurs.

Comment sont organisées les données de l'image ?

Après entête et table de conversion, commence la série de données représentant l'image. Elles sont organisées en une suite de paquets dont chaque paquet représente une ligne horizontale de l'image. Chaque ligne doit être un multiple de 8. Le premier paquet de données est la dernière ligne, et le dernier paquet représente la première ligne de l'image. Puisque chaque paquet de données doit être un multiple de 8, des octets (neutres) sont ajoutés si la largeur en pixels de l'image n'est pas un multiple de 8.

Les images comptant 1, 16, 256, 32 K ou 64 K occupent moins d'espace, mais nécessitent une table de conversion.

Structure de l'entête d'un fichier Bitmap. Documentation MicroSoft (Syntaxe C).

typedef struct tagBITMAPFILEHEADER {
WORD bfType;
DWORD bfSize;
WORD bfReserved1;
WORD bfReserved2;
DWORD bfOffBits;
} BITMAPFILEHEADER;

L'entête du fichier fait donc 14 octets.

Structure de l'entête d'un Bitmap. Documentation MicroSoft (Syntaxe C).

typedef struct tagBITMAPINFOHEADER{
DWORD biSize;
LONG biWidth;
LONG biHeight;
WORD iplanes;
WORD biBitCount
DWORD biCompression;
DWORD biSizeImage;
LONG biXPelsPerMeter;
LONG biYPelsPerMeter;
DWORD biClrUsed;
DWORD biClrImportant;
} BITMAPINFOHEADER;

L'entête de définition du Bitmap fait 40 octets (soit un total de 54).

XXII-B - Exemple d'une palette de conversion en 256 couleurs

XXII-C - Programme permettant de connaître les caractéristiques d'une image Bitmap sans charger tout le fichier

Voici un petit programme qui se contente de lire l'entête d'un fichier Bitmap (.bmp) et d'afficher ses caractéristiques.

N.B. Je l'ai complété d'un objet TImage et du chargement de l'image en question uniquement pour avoir une visualisation du fichier en question.

Source ProjetBitMap1.zip

XXIII - Liste des codes d'erreurs Entrées/Sorties DELPHI et WINDOWS

Les codes d'erreurs E/S DELPHI sont retournés par la fonction IOResult.
ATTENTION : la directive de compilation $I doit être paramétré en conséquence.

Codes DELPHI

Messages

Codes WINDOWS

Messages

Traductions

2

Fichier non trouvé

2

ERROR_File_NOT_FOUND

3

Chemin non trouvé

3

ERROR_PATH_NOT_FOUND

4

Trop de fichiers ouverts

4

ERROR_TOO_MANY_OPEN_FILES

5

Accès au fichier refusé

5

ERROR_ACCESS_DENIED

6

Identificateur de fichier invalide

6

ERROR_INVALID_HANDLE

12

Code d'accès au fichier invalide

12

ERROR_INVALID_ACCESS

15

Numéro d'unité de disque invalide

15

ERROR_INVALID_DRIVE

16

Ne peut supprimer le répertoire courant

16

ERROR_CURRENT_DIRECTORY

17

Ne peut renommer de disque à disque

17

ERROR_NOT_SAME_DEVICE

100

Erreur de lecture

30

ERROR_READ_FAULT

101

Erreur d'écriture

29

ERROR_WRITE_FAULT

102

Variable non assignée à un fichier

103

Fichier non ouvert

110

ERROR_OPEN_FAILED

104

Fichier non ouvert en lecture

105

Fichier non ouvert en écriture

106

Format numérique invalide

1006

ERROR_FILE_INVALID

150

Disque protégé en écriture

108

ERROR_DRIVE_LOCKED

151

Unité de disque non reconnue par le système

20

ERROR_BAD_UNIT

152

Unité de disque non prête

153

Commande inconnue

154

Erreur d'intégrité des données lues

155

Disque spécifié invalide

156

Erreur de positionnement des têtes de lecture

157

Type de média invalide

158

Secteur non trouvé

27

ERROR_SECTOR_NOT_FOUND

159

Erreur imprimante- plus de papier

160

Erreur d'écriture sur le périphérique

161

Erreur de lecture sur le périphérique

162

Erreur liée au matériel

18

ERROR_NO_MORE_FILES

19

ERROR_WRITE_PROTECT

Fichier protégé en écriture (lecture seule)

26

ERROR_NOT_DOS_DISK

Disque non DOS

80

ERROR_FILE_EXISTS

Le fichier existe déjà

107

ERROR_DISK_CHANGE

Erreur lors d'un changement de disque

112

ERROR_DISK_FULL

Disque plein

161

ERROR_BAD_PATHNAME

Mauvais chemin

266

ERROR_CANNOT_COPY

Copie impossible

267

ERROR_DIRECTORY

Erreur de répertoire

2401

ERROR_OPEN_FILES

Erreur d'ouverture de fichier

XXIV - Sources des exercices

Sources au format ZIP :

-A- Sources des exercices
-B- Editeur CSV
-C- Projet Bitmap
-D- Fichier exemple d'un agenda (pour notre didacticiel).
ATTENTION : Cet agenda d'exemple n'est compatible qu'avec les exercices 4 et 5.

XXV - Liens divers et bibliographie

-1- Gestion de fichier FAQ Developpez.com
-2- DELPHI SOURCES Developpez.com, fichiers
-3- FICHIERS INI FAQ Developpez.com
-4- Les programmes en code source des livres de Jhon Colibri

XXVI - Que réserve la seconde partie du tutoriel ?

Tout d'abord, une autre technique pour intégrer dans notre agenda n'importe quel type de données, en dehors de la structure. La photo sera ajoutée à la suite, et un RichTextFile à la suite de la photo.

Je parlerai également des API WINDOWS, des Handles de fichier et de diverses choses.

L'édition hexadécimale sera abordée également.

XXVII - L'article complet au format PDF

L'article au format PDF.