Streaming components/fr: what changed
Revision 1 to 2. Removed lines have a minus sign; added lines have a plus sign.
| Old | New | Line | |
|---|---|---|---|
| 1 | 1 | ## Introduction | |
| 2 | 2 | ||
| 3 | 3 | Normalement, quand vous voulez stocker des données sur le disque ou le réseau, vous devez écrire le code pour le chargement et l'enregistrement de chaque propriété. Ce tutoriel décrit comment écrire des classes qui peuvent être chargées et enregistrées à partir de flux sans charge supplémentaire de code de chargement/enregistrement, tout cela en utilisant la **RTTI**. | |
| 4 | 4 | ||
| 5 | 5 | Vous trouverez un exemple dans les sources de Lazarus démontrant comment enregistrer un **TGroupBox** avec un enfant **TCheckBox** vers un flux et relire ce flux pour créer une copie des deux composants. Voir \<lazaruspath\>/examples/componentstreaming/ | |
| 6 | 6 | ||
| 7 | 7 | En combinaison avec [[RTTI controls/fr|Les contrôles RTTI]], vous pouvez réduire au minimum la quantité de code nécessaire pour connecter les données du programme avec le GUI et le Disque/Réseau. | |
| 8 | 8 | ||
| 9 | 9 | ## TComponent / TPersistent | |
| 10 | 10 | ||
| 11 | 11 | La classe **TPersistent** est définie dans l'unité **Classes** et utilise la directive de compilation **{\$M+}** . Cette directive dit au compilateur de créer de l'information pendant le temps d'exécution (**RTTI**). Lui et tous ses descendants comprennent alors une nouvelle section de classe **published**. Les propriétés **published** sont visibles comme si elles étaient **public**, mais leur structure est aussi accessible pendant le temps d'exécution. Cela signifie que toute propriété **published** peut être lue et écrite pendant ce temps d'exécution. Par exemple, l'EDI emploie cette technique pour travailler avec des composants dont il n'a jamais entendu parler. | |
| 12 | 12 | ||
| 13 | 13 | **TComponent** étend les possibilités de **TPersistent** par sa capacité à posséder des composants enfants. Cette capacité est importante pour le traitement des flux : un composant est le composant racine (appelé parfois **lookup root**) qui gère une liste de composants enfants. | |
| 14 | 14 | ||
| 15 | 15 | ## TReader / TWriter | |
| 16 | 16 | ||
| 17 | 17 | Ce sont les classes qui permettent d'écrire/de lire un **TComponent** vers/depuis un flux (Voir **CreateLRSReader** et **CreateLRSWriter**). Elles emploient un **Pilote** pour lire/écrire dans un format spécial. A l'heure actuelle, il y a un lecteur (**TLRSObjectReader**), un outil d'écriture (**TLRSObjectWriter**) pour le format d'objet binaire défini dans l'unité **LResources** et un outil d'écriture (**TXMLObjectWriter**) pour **TDOMDocument** défini dans **Laz_XMLStreaming**. L'unité **LResources** contient également des fonctions pour convertir le format binaire en texte et l'inverse (**LRSObjectBinaryToText**, **LRSObjectTextToBinary**). La bibliothèque LCL utilise de préférence le codage **UTF8** pour les chaînes de caractères tandis que Delphi préfère **Widestrings** . Par conséquent, vous disposez aussi de quelques fonctions de conversion si bien que vous pouvez tout aussi bien travailler avec des données de flux provenant de Lazarus qu'avec des données au format binaire de Delphi. | |
| 18 | 18 | ||
| 19 | 19 | ## Streaming Collections | |
| 20 | 20 | ||
| 21 | 21 | Consultez [[TCollection/fr|TCollection/fr#Streaming]] | |
| 22 | 22 | ||
| 23 | 23 | Il s'agit d'un exemple complet qui montre comment utiliser les classes **TCollectionItem**, **TCollection** et les gérer par des flux grâce à **TComponent**. | |
| 24 | 24 | ||
| 25 | 25 | ## Écrire votre propre composant - Partie 1 | |
| 26 | 26 | ||
| 27 | 27 | Un composant personnalisé peut être aussi simple que : | |
| 28 | 28 | ||
| 29 | - | ||
| 30 | - | ||
| 31 | - | ||
| 32 | - | ||
| 33 | - | ||
| 34 | - | ||
| 35 | - | ||
| 29 | + | ``` pascal | |
| 30 | + | type | |
| 31 | + | TMyComponent = class(TComponent) | |
| 32 | + | private | |
| 33 | + | FID: integer; | |
| 34 | + | published | |
| 35 | + | property ID: integer read FID write FID; | |
| 36 | + | end; | |
| 37 | + | ``` | |
| 36 | 38 | ||
| 37 | 39 | ## Écrire un composant dans un flux | |
| 38 | 40 | ||
| 39 | 41 | L'unité [[doc:lcl/lresources|LResources]] a une fonction pour cela : | |
| 40 | 42 | ||
| 41 | - | ||
| 43 | + | ``` pascal | |
| 44 | + | procedure WriteComponentAsBinaryToStream(AStream: TStream; AComponent: TComponent); | |
| 45 | + | ``` | |
| 42 | 46 | ||
| 43 | 47 | Elle écrit un composant dans le format binaire du flux. Par exemple : | |
| 44 | 48 | ||
| 45 | 49 | procedure TForm1.Button1Click(Sender: TObject); | |
| 46 | 50 | var | |
| 47 | 51 | AStream: TMemoryStream; | |
| 48 | 52 | begin | |
| 49 | 53 | AStream:=TMemoryStream.Create; | |
| 50 | 54 | try | |
| 51 | 55 | WriteComponentAsBinaryToStream(AStream,AGroupBox); | |
| 52 | 56 | ... enregistre le flux quelque part ... | |
| 53 | 57 | finally | |
| 54 | 58 | AStream.Free; | |
| 55 | 59 | end; | |
| 56 | 60 | end; | |
| 57 | 61 | ||
| 58 | 62 | ## Lecture d'un composant depuis un flux | |
| 59 | 63 | ||
| 60 | 64 | L'unité **LResources** a une fonction pour cela : | |
| 61 | 65 | ||
| 62 | - | ||
| 63 | - | ||
| 66 | + | ``` pascal | |
| 67 | + | procedure ReadComponentFromBinaryStream(AStream: TStream; | |
| 68 | + | var RootComponent: TComponent; OnFindComponentClass: TFindComponentClassEvent; TheOwner: TComponent = nil); | |
| 69 | + | ``` | |
| 64 | 70 | ||
| 65 | 71 | - **AStream** est le flux contenant un composant au format binaire. | |
| 66 | 72 | - **RootComponent** est soit un composant existant dont les données seront écrasées, soit c'est **nil** et un nouveau composant sera créé. | |
| 67 | 73 | - **OnFindComponentClass** est une fonction qui est utilisée par **TReader** pour obtenir la classe à partir des noms de classe dans le flux. Par exemple : | |
| 68 | 74 | ||
| 69 | 75 | procedure TCompStreamDemoForm.OnFindClass(Reader: TReader; | |
| 70 | 76 | const AClassName: string; var ComponentClass: TComponentClass); | |
| 71 | 77 | begin | |
| 72 | 78 | if CompareText(AClassName,'TGroupBox')=0 then | |
| 73 | 79 | ComponentClass:=TGroupBox | |
| 74 | 80 | else if CompareText(AClassName,'TCheckBox')=0 then | |
| 75 | 81 | ComponentClass:=TCheckBox; | |
| 76 | 82 | end; | |
| 77 | 83 | ||
| 78 | 84 | - **TheOwner** est le propriétaire du composant pendant la création d'un nouveau composant. | |
| 79 | 85 | ||
| 80 | 86 | ## Propriétés pouvant être stockées dans un flux | |
| 81 | 87 | ||
| 82 | 88 | Il y a quelques limitations concernant les types **TReader**/**TWriter** qui peuvent être stockés dans un flux : | |
| 83 | 89 | ||
| 84 | 90 | - Les types de base sont admis pour un flux : string, integer, char, single, double, extended, byte, word, cardinal, shortint, les pointeurs de méthode, etc. ; | |
| 85 | 91 | - **TPersistent** et ses descendants sont aussi admis pour un flux ; | |
| 86 | 92 | - Les enregistrements (**records**), les objets et les classes ne descendant pas de **TPersistent** ne peuvent pas être stockés dans un flux . Pour les stocker dans un flux, vous devez surcharger certaines méthodes de **TReader**/**TWriter**. Voir ci-dessous Mise en flux de données courantes - DefineProperties. | |
| 87 | 93 | ||
| 88 | 94 | ## Mise en flux de données courantes - DefineProperties. | |
| 89 | 95 | ||
| 90 | 96 | Vous pouvez mettre en flux des données arbitraires additionnelles en écrasant **DefineProperties**. Ceci permet de mettre en flux toutes les données qui n'ont aucun type de base. Par exemple, pour mettre en flux une variable **FMyRect: TRect** de votre composant , ajoutez les trois méthodes suivantes à votre composant : | |
| 91 | 97 | ||
| 92 | 98 | procedure DefineProperties(Filer: TFiler); override; | |
| 93 | 99 | procedure ReadMyRect(Reader: TReader); | |
| 94 | 100 | procedure WriteMyRect(Writer: TWriter); | |
| 95 | 101 | ||
| 96 | 102 | avec le code suivant : | |
| 97 | 103 | ||
| 98 | 104 | procedure TMyComponent.DefineProperties(Filer: TFiler); | |
| 99 | 105 | var | |
| 100 | 106 | MyRectMustBeSaved: Boolean; | |
| 101 | 107 | begin | |
| 102 | 108 | inherited DefineProperties(Filer); | |
| 103 | 109 | MyRectMustBeSaved:=(MyRect.Left<>0) | |
| 104 | 110 | or (MyRect.Top<>0) | |
| 105 | 111 | or (MyRect.Right<>0) | |
| 106 | 112 | or (MyRect.Bottom<>0); | |
| 107 | 113 | Filer.DefineProperty('MyRect',@ReadMyRect,@WriteMyRect,MyRectMustBeSaved); | |
| 108 | 114 | end; | |
| 109 | 115 | ||
| 110 | 116 | procedure TMyComponent.ReadMyRect(Reader: TReader); | |
| 111 | 117 | begin | |
| 112 | 118 | with Reader do begin | |
| 113 | 119 | ReadListBegin; | |
| 114 | 120 | FMyRect.Left:=ReadInteger; | |
| 115 | 121 | FMyRect.Top:=ReadInteger; | |
| 116 | 122 | FMyRect.Right:=ReadInteger; | |
| 117 | 123 | FMyRect.Bottom:=ReadInteger; | |
| 118 | 124 | ReadListEnd; | |
| 119 | 125 | end; | |
| 120 | 126 | end; | |
| 121 | 127 | ||
| 122 | 128 | procedure TMyComponent.WriteMyRect(Writer: TWriter); | |
| 123 | 129 | begin | |
| 124 | 130 | with Writer do begin | |
| 125 | 131 | WriteListBegin; | |
| 126 | 132 | WriteInteger(FMyRect.Left); | |
| 127 | 133 | WriteInteger(FMyRect.Top); | |
| 128 | 134 | WriteInteger(FMyRect.Right); | |
| 129 | 135 | WriteInteger(FMyRect.Bottom); | |
| 130 | 136 | WriteListEnd; | |
| 131 | 137 | end; | |
| 132 | 138 | end; | |
| 133 | 139 | ||
| 134 | 140 | Cela sauvegardera **MyRect** en tant que propriété **MyRect**. | |
| 135 | 141 | ||
| 136 | 142 | Si vous mettez en flux beaucoup de **TRect**, vous ne voudrez probablement pas écrire ce code chaque fois. L'unité **LResources** contient un exemple qui montre comment écrire une procédure pour définir une propriété **rect** : | |
| 137 | 143 | ||
| 138 | - | ||
| 139 | - | ||
| 144 | + | ``` pascal | |
| 145 | + | procedure DefineRectProperty(Filer: TFiler; const Name: string; ARect, DefaultRect: PRect); | |
| 146 | + | ``` | |
| 140 | 147 | ||
| 141 | 148 | De cette façon, le code ci-dessus peut être réduit à : | |
| 142 | 149 | ||
| 143 | 150 | procedure TMyComponent.DefineProperties(Filer: TFiler); | |
| 144 | 151 | begin | |
| 145 | 152 | inherited DefineProperties(Filer); | |
| 146 | 153 | DefineRectProperty(Filer,'MyRect',@FMyRect,nil); | |
| 147 | 154 | end; | |
| 148 | 155 | ||
| 149 | 156 | ## Écrire votre propre composant - Partie 2 | |
| 150 | 157 | ||
| 151 | 158 | L'exemple décrit peut être étendu. Ainsi, nous pouvons employer des propriétés arbitraires avec seulement quelques lignes de code : | |
| 152 | 159 | ||
| 153 | 160 | type | |
| 154 | 161 | TMyComponent = class(TComponent) | |
| 155 | 162 | private | |
| 156 | 163 | FID: integer; | |
| 157 | 164 | FRect1: TRect; | |
| 158 | 165 | FRect2: TRect; | |
| 159 | 166 | protected | |
| 160 | 167 | procedure DefineProperties(Filer: TFiler); override; | |
| 161 | 168 | public | |
| 162 | 169 | property Rect1: TRect read FRect1 write FRect1; | |
| 163 | 170 | property Rect2: TRect read FRect2 write FRect2; | |
| 164 | 171 | published | |
| 165 | 172 | property ID: integer read FID write FID; | |
| 166 | 173 | end; | |
| 167 | 174 | ||
| 168 | 175 | procedure TMyComponent.DefineProperties(Filer: TFiler); | |
| 169 | 176 | begin | |
| 170 | 177 | inherited DefineProperties(Filer); | |
| 171 | 178 | DefineRectProperty(Filer,'Rect1',@FRect1,nil); | |
| 172 | 179 | DefineRectProperty(Filer,'Rect2',@FRect2,nil); | |
| 173 | 180 | end; | |
| 174 | 181 | ||
| 175 | 182 | Ce composant peut maintenant être sauvegardé, chargé ou utilisé par [[RTTI controls/fr|Les contrôles RTTI]]. Vous n'avez pas besoin d'écrire quoi que ce soit d'autre. | |
| 176 | 183 | ||
| 177 | 184 | ## Écrire et lire des composants depuis/vers LFM | |
| 178 | 185 | ||
| 179 | 186 | Consultez les fonctions **ReadComponentFromTextStream** et **WriteComponentAsTextToStream** de l'unité **lresources** pour des exemples. | |
| 180 | 187 | ||
| 181 | 188 | ## Écrire et lire des composants depuis/vers XML | |
| 182 | 189 | ||
| 183 | 190 | Mettre en flux des composants est simple : Consultez l'exemple dans lazarus/examples/xmlstreaming/. | |
| 184 | 191 | ||
| 185 | 192 | ## Noms | |
| 186 | 193 | ||
| 187 | 194 | - Tous les composants appartenant à un composant (**Owner**) doivent porter des noms distincts. Il en est de même pour deux fiches possédées par une application ou de deux étiquettes (**labels**) d'une fiche. Mais deux étiquettes appartenant à deux fiches distinctes peuvent porter le même nom et une fiche peut porter le même nom qu'un de ses enfants. | |
| 188 | 195 | ||
| 189 | 196 | - **TComponent.Name** peut rester vide et vous pouvez avoir plus d'un composant sans nom. **TWriter** écrira la propriété, mais **TReader** ne trouvera pas le composant en question si bien que la lecture échouera. Par conséquent, l'inspecteur d'objet de l'EDI ne tolère pas cette situation. | |
| 190 | 197 | ||
| 191 | 198 | - Les noms doivent être des identificateurs Pascal corrects ([IsValidIdent](http://lazarus-ccr.sourceforge.net/docs/rtl/sysutils/isvalidident.html)). | |
| 192 | 199 | ||
| 193 | 200 | - Lors d'une référence à d'autres fiches, tous les composants racines (**forms**, **datamodules**, ...) référencés eux-mêmes par d'autres (**forms**, etc.) doivent porter des noms uniques. Ils n'ont pas besoin d'être possédés par l'application, mais le programmeur doit alors par lui-même vérifier que leurs noms sont uniques. Les fiches et les modules de données peuvent être retrouvés grâce à l'objet **Screen** dans lequel toutes les fiches et tous les modules de données s'enregistrent automatiquement.. | |
| 194 | 201 | ||
| 195 | 202 | - Vous pouvez créer de nombreuses fiches portant le nom **Form1** (par exemple, grâce à **TForm1.Create(nil)**). Si une **Form2** fait référence à **Form1.OpenDialog**, la première **Form1** dans **Screen** sera utilisée. | |
| 196 | 203 | ||
| 197 | 204 | - Une fiche **Form1** et un cadre imbriqué peuvent posséder tous les deux un enfant **Label1**. Quand l'étiquette **Label1** sera référencée, ce sera de manière univoque pour la fiche entière, donc en incluant les cadres imbriqués. Il est par conséquent vivement recommandé de donner un nom unique à chaque élément. | |
| 198 | 205 | ||
| 199 | 206 | - Correction globale : **TReader** lit le flux d'un composant. S'il rencontre un cadre imbriqué, un second **TReader** est créé afin d'en lire le flux. Ensuite, le lecteur retourne à son point de départ et continue la lecture. Les référence à d'autres composants (par exemple **Form1.Button1**) sont enregistrées dans une liste de correction dans les classes des unités (Voir [GetFixupReferenceNames](http://lazarus-ccr.sourceforge.net/docs/rtl/classes/getfixupreferencenames.html)). Les références sont rectifiées après la lecture. | |
| 200 | 207 | ||
| 201 | 208 | - **TReader** et **TWriter** utilisent le nom spécial **Owner** pour se référer au propriétaire en cours. | |
| 202 | 209 | ||
| 203 | 210 | ## Conclusion | |
| 204 | 211 | ||
| 205 | 212 | La RTTI est un mécanisme puissant qui peut être employé pour facilement mettre en flux des classes entières et qui évite d'écrire beaucoup de code de chargement/enregistrement pénible. | |
| 206 | 213 | ||
| 207 | 214 | Voir également : | |
| 208 | 215 | ||
| 209 | 216 | [[RTTI controls/fr|Les contrôles RTTI]] | |
| 210 | 217 | ||
| 211 | 218 | --- | |
| 212 | 219 | *From the Free Pascal wiki, page "Streaming components/fr", as it stood on 2017-05-30 after a change by Gillou58, out of the wiki's dump of 2024-05-26. Copyright stays with the author who wrote it, according to the old wiki's [[Licensing]] page. If you have concerns about this material, [message the admin](/wiki-contact).* |
