J'ai besoin de stocker une liste temporaire de documents et je pensais qu'une TList
serait un bon moyen de le faire? Cependant, je ne suis pas sûr de savoir comment faire cela avec un TList
et je me demandais si c'était le meilleur et aussi si quelqu'un avait des exemples montrant comment faire cela.
Le moyen le plus simple est de créer votre propre descendant de TList
. Voici un exemple d'application de console rapide à illustrer:
program Project1;
{$APPTYPE CONSOLE}
uses
SysUtils, Classes;
type
PMyRec=^TMyRec;
TMyRec=record
Value: Integer;
AByte: Byte;
end;
TMyRecList=class(TList)
private
function Get(Index: Integer): PMyRec;
public
destructor Destroy; override;
function Add(Value: PMyRec): Integer;
property Items[Index: Integer]: PMyRec read Get; default;
end;
{ TMyRecList }
function TMyRecList.Add(Value: PMyRec): Integer;
begin
Result := inherited Add(Value);
end;
destructor TMyRecList.Destroy;
var
i: Integer;
begin
for i := 0 to Count - 1 do
FreeMem(Items[i]);
inherited;
end;
function TMyRecList.Get(Index: Integer): PMyRec;
begin
Result := PMyRec(inherited Get(Index));
end;
var
MyRecList: TMyRecList;
MyRec: PMyRec;
tmp: Integer;
begin
MyRecList := TMyRecList.Create;
for tmp := 0 to 9 do
begin
GetMem(MyRec, SizeOf(TMyRec));
MyRec.Value := tmp;
MyRec.AByte := Byte(tmp);
MyRecList.Add(MyRec);
end;
for tmp := 0 to MyRecList.Count - 1 do
Writeln('Value: ', MyRecList[tmp].Value, ' AByte: ', MyRecList[tmp].AByte);
WriteLn(' Press Enter to free the list');
ReadLn;
MyRecList.Free;
end.
Cela élimine un certain nombre de choses:
Comme Remy et Warren l’ont dit, c’est un peu plus de travail car il faut allouer de la mémoire lorsque vous ajoutez de nouveaux enregistrements.
Tout d'abord, si vous souhaitez combiner une TList classique avec Records, vous devez:
Combiner des listes avec des enregistrements nécessite un travail de "gestion du pointeur et du tas" tellement important qu'une telle technique ne serait à la portée d'un expert.
Les solutions de rechange à ce que vous avez demandé utilisent toujours quelque chose appelé "TList", y compris l'utilisation d'un style TList de style générique.Collections, avec des types Record, qui présenterait tous les avantages de TList, mais vous obligerait à faire beaucoup des copies d’enregistrement pour y insérer des données.
Les manières les plus idiomatiques de Delphi de faire ce que vous demandez sont les suivantes:
utilisez un TList ou TObjectList avec un types de classe au lieu d'un enregistrement. Généralement, vous finissez par sous-classer TList ou TObjectList dans ce cas.
Utilisez un tableau dynamique de types d'enregistrement, mais sachez qu'il est plus difficile de trier un type de tableau et que développer un type de tableau au moment de l'exécution n'est pas aussi rapide qu'avec un TList.
Utilisez generics.Collections TList avec vos classes. Cela vous permet d'éviter de sous-classer TList ou TObjectList chaque fois que vous souhaitez utiliser une liste avec une classe différente.
Un exemple de code montrant les tableaux dynamiques:
TMyRec = record
///
end;
TMyRecArray = array of TMyRec;
procedure Demo;
var
myRecArray:TMyRecArray;
begin
SetLength(myRecArray,10);
end;
Maintenant, pour quelques informations de base sur les raisons pour lesquelles TList n’est pas facile à utiliser avec les types d’enregistrement:
TList convient mieux aux types de classe, car une variable de type 'TMyClass', où 'type TMyClass = class .... end;' peut être facilement "désigné" comme une valeur de pointeur, ce qui est ce que TList tient.
Les variables de type Record sont des types de valeur dans Delphi, alors que les valeurs de classe sont implicitement des valeurs par référence. Vous pouvez considérer les valeurs par référence comme des indicateurs furtifs. Vous n'avez pas besoin de les déréférencer pour obtenir leur contenu, mais lorsque vous l'ajoutez à une liste TList, vous ajoutez en fait un pointeur à la liste TList, sans en faire une copie ni en allouant une nouvelle mémoire.
La réponse de Remy vous montre littéralement comment faire exactement ce que vous voulez, et je n’écris ma réponse que parce que je veux vous avertir des détails de ce que vous demandez et vous suggérer d’envisager des solutions de rechange.
Vous pouvez jeter un oeil à notre TDynArray wrapper . Il est défini dans une unité Open Source, fonctionnant de Delphi 6 à XE.
Avec TDynArray
, vous pouvez accéder à n’importe quel tableau dynamique (tel que TIntegerDynArray = array of integer
ou TRecordDynArray = array of TMyRecord
) à l’aide des propriétés et méthodes de type TList
-, par exemple. Count, Add, Insert, Delete, Clear, IndexOf, Find, Sort
et de nouvelles méthodes telles que LoadFromStream, SaveToStream, LoadFrom
et SaveTo
qui permettent la sérialisation binaire rapide de tout tableau dynamique, même contenant des chaînes ou des enregistrements - une méthode CreateOrderedIndex
est également disponible pour créer un index individuel en fonction du contenu du tableau dynamique. Vous pouvez également sérialiser le contenu du tableau en JSON, si vous le souhaitez. Les méthodes Slice, Reverse
ou Copy
sont également disponibles.
Il gérera un tableau dynamique d'enregistrements, et même des enregistrements au sein d'enregistrements, avec des chaînes ou d'autres tableaux dynamiques à l'intérieur.
Lorsque vous utilisez une variable Count
externe, vous pouvez accélérer considérablement l'ajout d'éléments dans le tableau dynamique référencé.
type
TPerson = packed record
sCountry: string;
sFullName: string;
sAddress: string;
sCity: string;
sEmployer: string;
end;
TPersons = array of TPerson;
var
MyPeople: TPersons;
(...)
procedure SavePeopleToStream(Stream: TMemoryStream);
var aPeople: TPerson;
aDynArray: TDynArray;
begin
aDynArray.Init(TypeInfo(TPersons),MyPeople);
aPeople.sCountry := 'France';
aPeople.sEmployer := 'Republique';
aDynArray.Add(aPeople);
aDynArray.SaveToStream(Stream);
end; // no try..finally Free needed here
Il existe également une classe TDynArrayHashed
, qui permet le hachage interne d'un contenu de tableau dynamique. Il est très rapide et capable de hacher n'importe quel type de données (il existe des hachages standard pour les chaînes, mais vous pouvez en fournir vous-même - même la fonction de hachage peut être personnalisée).
Notez que TDynArray
et TDynArrayHashed
sont simplement des enveloppeurs autour d'une variable de tableau dynamique existante. Vous pouvez donc initialiser un wrapper TDynArray
sur besoin pour accéder plus efficacement à tout tableau dynamique Delphi natif.
Vous pouvez utiliser TList pour cela, par exemple:
type
pRec = ^sRec;
sRec = record
Value: Integer;
...
end;
var
List: TList;
Rec: pRec;
I: Integer;
begin
List := TList.Create;
try
for I := 1 to 5 do begin
GetMem(Rec);
try
Rec^.Value := ...;
...
List.Add(Rec);
except
FreeMem(Rec);
raise;
end;
end;
...
for I := 0 to List.Count-1 do
begin
Rec := pRec(List[I]);
...
end;
...
for I := 0 to List.Count-1 do
FreeMem(pRec(List[I]));
List.Clear;
finally
List.Free;
end;
end;
Nous venons de rencontrer un problème similaire avec une liste générique d'enregistrements. Espérons que le code suivant psuedo aide.
type
PPat = ^TPat;
TPat = record
data: integer;
end;
...
var
AList: TList<PPat>;
...
procedure TForm1.Button1Click(Sender: TObject);
var
obj: PPat;
begin
obj := AList[0];
obj.data := 1;
Assert(obj.data = AList[0].data); // correct
end;
procedure TForm1.FormCreate(Sender: TObject);
var
obj: PPat;
begin
AList := TList<PPat>.Create;
GetMem(obj, SizeOf(TPat)); // not shown but need to FreeMem when items are removed from the list
obj.data := 2;
AList.Add(obj);
end;
Si vous utilisez une version antérieure de Delphi où les génériques ne sont pas présents, envisagez d'hériter de TList et de remplacer la méthode Notify. Lors de l'ajout d'un élément, allouez de la mémoire, copiez le contenu de la mémoire du pointeur ajouté et remplacez le contenu de la liste. Lors du retrait, libérez simplement de la mémoire.
TOwnedList = class(TList)
private
FPtrSize: integer;
protected
procedure Notify(Ptr: Pointer; Action: TListNotification); override;
public
constructor Create(const APtrSize: integer);
end;
constructor TOwnedList.Create(const APtrSize: integer);
begin
inherited Create();
FPtrSize := APtrSize;
end;
procedure TOwnedList.Notify(Ptr: Pointer; Action: TListNotification);
var
LPtr: Pointer;
begin
inherited;
if (Action = lnAdded) then begin
GetMem(LPtr, FPtrSize);
CopyMemory(LPtr, Ptr, FPtrSize); //May use another copy kind
List^[IndexOf(Ptr)] := LPtr;
end else if (Action = lnDeleted) then begin
FreeMem(Ptr, FPtrSize);
end;
end;
Usage:
...
LList := TOwnedList.Create(SizeOf(*YOUR RECORD TYPE HERE*));
LList.Add(*YOU RECORD POINTER HERE*);
...
Tout dépend du type de données que vous souhaitez stocker.
Vous pouvez envisager d’utiliserTCollection
etTCollectionItem
.
Voici le code (edité) d'une unité de travail dans laquelle j'ai utilisé TCollection
pour lire une liste de définitions de rapport à partir d'un dossier. Chaque rapport consistait en une sorte de modèle et une instruction SQL qui devaient être stockés avec un nom de fichier.
Comme il est édité et utilise certaines de mes propres unités (TedlFolderRtns lit les fichiers dans une liste interne, pour n'en nommer qu'une), cet exemple est assez simple pour être utile. En remplaçant quelques-uns, vous pouvez vous adapter à tous vos besoins.
Recherchez l'aide de TCollection, vous pouvez en faire beaucoup. Et cela permet de garder votre code manipulé dans une structure de type classe.
unit cReports;
interface
uses
SysUtils, Classes, XMLDoc, XMLIntf, Variants,
// dlib - Edelcom
eIntList, eProgSettings,eFolder ;
type
TReportDefItem = class(TCollectionItem)
private
fSql: string;
fSkeleton: string;
fFileName: string;
procedure Load;
procedure SetFileName(const Value: string);
public
constructor Create(Collection:TCollection); override;
destructor Destroy ; override;
property FileName: string read fFileName write SetFileName;
property Sql : string read fSql write fSql;
property Skeleton : string read fSkeleton write fSkeleton;
end;
TReportDefList = class(TCollection)
private
function OsReportFolder: string;
function GetAction(const Index: integer): TReportDefItem;
public
constructor Create(ItemClass: TCollectionItemClass);
destructor Destroy; override;
procedure LoadList;
function Add : TReportDefItem;
property Action [ const Index:integer ]: TReportDefItem read GetAction;
end;
implementation
{ TReportDefList }
constructor TReportDefList.Create(ItemClass: TCollectionItemClass);
begin
inherited;
end;
destructor TReportDefList.Destroy;
begin
inherited;
end;
function TReportDefList.Add: TReportDefItem;
begin
Result := TReportDefItem( Add() );
end;
function TReportDefList.GetAction(const Index: integer): TReportDefItem;
begin
if (Index >= 0) and (Index < Count)
then Result := TReportDefItem( Items[Index] )
else Result := Nil;
end;
procedure TReportDefList.LoadList;
var Folder : TedlFolderRtns;
i : integer;
Itm : TReportDefItem;
begin
Folder := TedlFolderRtns.Create;
try
Folder.FileList( OsReportFolder,'*.sw.xml', False);
for i := 0 to Folder.ResultListCount -1 do
begin
Itm := Add();
Itm.FileName := Folder.ResultList[i];
end;
finally
FreeAndNil(Folder);
end;
end;
function TReportDefList.OsReportFolder: string;
begin
Result := Application.ExeName + '_RprtDef';
end;
{ TReportDefItem }
constructor TReportDefItem.Create(Collection: TCollection);
begin
inherited;
fSql := '';
fSkeleton := '';
end;
destructor TReportDefItem.Destroy;
begin
inherited;
end;
procedure TReportDefItem.Load;
var XMLDoc : IXMLDocument;
TopNode : IXMLNode;
FileNode : IXmlNode;
iWebIndex, iRemoteIndex : integer;
sWebVersion, sRemoteVersion: string;
sWebFileName: string;
begin
if not FileExists(fFileName ) then Exit;
XMLDoc := TXMLDocument.Create(nil);
try
XMLDoc.LoadFromFile( fFileName );
XMLDoc.Active := True;
TopNode := XMLDoc.ChildNodes.FindNode('sw-report-def');
if not Assigned(TopNode) then Exit;
FileNode := TopNode.ChildNodes.First;
while Assigned(FileNode) do
begin
fSql := VarToStr( FileNode.Attributes['sql'] );
fSkeleton := VarToStr( FileNode.Attributes['skeleton'] );
FileNode := FileNode.NextSibling;
end;
XMLDoc.Active := False;
finally
XMLDoc := Nil;
end;
end;
procedure TReportDefItem.SetFileName(const Value: string);
begin
if fFileName <> Value
then begin
fFileName := Value;
Load;
end;
end;
end.
Utilisé comme :
fReports := TReportDefList.Create( TReportDefItem );
fReports.LoadList();