Delphi TList de dossiers
j'ai besoin de stocker une liste temporaire d'enregistrements et je pensais que TList
serait un bon moyen de faire ça? Cependant, je ne sais pas comment faire avec un TList
et je me demandais si c'est le meilleur et aussi si quelqu'un a des exemples de comment faire cela?
6 réponses
Le plus simple est de créer votre propre descendant de TList
. Voici un exemple rapide d'application de console à démontrer:
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.
Ceci élimine un couple de choses:
- il gère la libération de la mémoire.
- vous n'avez pas à tout taper pour l'utiliser.
comme Remy et Warren l'ont dit, c'est un peu plus de travail parce que vous devez allouer la mémoire quand vous ajoutez de nouveaux enregistrements.
tout d'abord, si vous voulez combiner un TList classique avec des enregistrements, vous aurez besoin de:
- Allouer vos dossiers sur le tas, pas sur la pile. Utilise GetMem comme Remy l'a fait.
- prendre l'adresse du dossier et l'ajouter au TList.
- lorsque vous supprimez un élément de la liste, et que vous l'utilisez, déréférez-le:
- rappelez-vous de libérer et de nettoyer, après.
combiner des listes avec des enregistrements demande tellement "pointeurs-et-tas-gestion" de travail que cette technique serait seulement dans les capacités d'un expert.
Les Alternatives à ce que vous avez demandé qui utilisent toujours quelque chose appelé "TList", comprennent l'utilisation d'un générique.collections style TList, avec des types de disques, qui auraient tous les avantages de TList, mais vous exigerait essentiellement de faire beaucoup de-record-copies entières pour obtenir des données dans elle.
les façons les plus idiomatiques Delphi de faire ce que vous demandez sont de soit:
utilisez un TList ou un TObjectList avec un type de classe au lieu d'un enregistrement. D'habitude, vous finissez par sous-classer TList ou TObjectList dans ce cas.
Utilisez un tableau dynamique de types D'enregistrements, mais sachez qu'il est plus difficile de trier un type de tableau, et que l'extension d'un type de tableau à l'exécution n'est pas aussi rapide qu'avec un TList.
Utilisez des génériques.Collections TList avec vos classes. Cela vous permet d'éviter sousclassing TList ou TObjectList chaque fois que vous voulez utiliser une liste avec une classe différente.
Un exemple de code montrant des 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 pourquoi TList n'est pas facile à utiliser avec les types D'enregistrement:
TList est mieux adapté pour une utilisation avec les types de classe, parce qu'une variable de type 'TMyClass', où 'Type TMyClass = class .... end; 'peut être facilement "appelé" comme une valeur de pointeur, ce qui est ce que TList tient.
Les Variables D'enregistrement de type sont des types de valeurs dans Delphi, alors que les valeurs de classe sont implicitement des valeurs de référence. Vous pouvez considérer les valeurs de référence comme des pointeurs furtifs. Vous n'avez pas à les déréférer pour accéder à leur contenu, mais quand vous l'ajoutez à un TList, vous ajoutez en fait juste un pointeur sur le TList, sans faire de copie ou allouer 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 mettre en garde contre les 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, allant de Delphi 6 à XE.
TDynArray
, vous pouvez accéder à un tableau dynamique (comme TIntegerDynArray = array of integer
ou TRecordDynArray = array of TMyRecord
) à l'aide de TList
propriétés et méthodes, par exemple,Count, Add, Insert, Delete, Clear, IndexOf, Find, Sort
et quelques nouvelles méthodes comme LoadFromStream, SaveToStream, LoadFrom
et SaveTo
qui permettent une sérialisation binaire rapide de n'importe quel tableau dynamique, même contenant des chaînes ou des enregistrements-a CreateOrderedIndex
méthode est également disponible pour créer des index selon le contenu du tableau dynamique. Vous pouvez également sérialiser le contenu du tableau dans JSON, si vous le souhaitez. Slice, Reverse
ou Copy
méthodes sont également disponibles.
il traitera un tableau dynamique d'enregistrements, et même d'enregistrements à l'intérieur d'enregistrements, avec des chaînes ou d'autres tableaux dynamiques à l'intérieur.
Lors de l'utilisation d'un externe Count
variable, vous pouvez accélérer beaucoup l'ajout d'éléments dans la visée de tableau dynamique.
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 y a aussi un TDynArrayHashed
classe, qui permettent 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 y a des hachoirs standards pour les chaînes, mais vous pouvez fournir votre propre - même la fonction de hachage peut être personnalisée).
Notez que TDynArray
et TDynArrayHashed
ne sont que des enveloppements autour d'une variable dynamic array existante. Vous pouvez donc initialiser un TDynArray
wrapper sur need, pour accéder plus efficacement à N'importe quel 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 ici avec une liste générique de dossiers. Espérons que le code psuedo suivant vous aidera.
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;
tout dépend du type de données que vous souhaitez stocker.
vous pourriez envisager d'utiliser TCollection
et TCollectionItem
.
Ici (modifié) code 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 se composait d'une sorte de modèle et D'une instruction SQL qui devait être stockée avec un nom de fichier.
Puisqu'il est édité, et utilise certaines de mes propres unités (TedlFolderRtns lit les fichiers dans une liste interne, pour n'en nommer qu'un), l'exemple est assez simple pour être utile. Avec quelques remplacements, vous pouvez vous adapter à tous vos besoins.
rechercher TCollection dans l'aide, vous pouvez faire beaucoup avec elle. Et il maintient votre manipulation de code bien groupé ensemble dans une structure de classe-comme.
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.
utiliser comme :
fReports := TReportDefList.Create( TReportDefItem );
fReports.LoadList();