Hallo,<P> ich habe eine TList (in der ich Records hineinspeichere) programmiert. Alles klappt so weit nur das Speichern und das Laden nicht. Egal was ich versuche es kommen entwender Fehlermeldungen oder nur Schei... raus. Auch im Forum (Suchen nach TList) hat mich nicht weitergebracht. Also hier meine Frage wie kann ich die TList in eine Datei speichern und wieder laden. Ein paar Programmzeilen wären nett. Danke.
Announcement
Collapse
No announcement yet.
TList laden und speichern
Collapse
X
-
Hallo,
TList speichert ein Array von Zeigern - also nur 32-Bit-Adresswerte auf einen Datenblock. Die Wahrscheinlichkeit, dass nach dem erneuten Starten der Anwendung (neuer Prozess mit nagelneuem 4 GByte virtuellen Adressraum) die "alten" Zeigerwerte noch zu irgend etwas sinnvollem zu verwenden sind, ist gleich 0.
Anstelle die Adresszeiger zu speichern, müssen die Objekte selbst mit der Fähigkeit nachgerüstet werden, sich persistent speichern zu können. Der Mechanismus wird in der Delphi-Hilfe unter <b>TReader</b> und <b>TWriter</b> beschrieben.
Wenn es nur um die Speicherung von Records mit fester Struktur geht, stellt sich die Frage, warum das Rad ständig neu erfunden werden muss. Sowohl mit <b>TClientDataSet</b> als auch mit dem <b>RecordSet</b>-Objekt von ADO stehen sofort einsatzfertige, sehr leistungsfähige und flexible Objekte zur Verfügung (Datenspeicherung in einer Datei). Während TClientDataSet nur in der Enterprise-Version zur Verfügung steht, kann jede Version von Delphi 4/5 direkt auf die COM-Objekt von ADO zugreifen (ADO Express wird also nicht unbedingt benötigt).


-
Hallo Andreas,<P> erst einmal Danke für die Erklärung, denn da hab ich wohl was falsch verstanden.<br>Mein Problem ist, eine kleine Datenbank zu programmieren (zwei Eingabefelder). Mir stehen aber in der Delphi 5.0 Std Version keine Tools zur Verfügung (weder TClientDataSet noch ADO).<br>Ich habe in der Hilfe unter TWriter und TReader nachgeschaut, aber so richtig schlau bin ich nicht daraus geworden.<BR>Und eins noch zum Schluß was hat der letzte Satz zu bedeuten (..."direkt auf die COM-Objekt von ADO zugreifen"...), heißt daß, ich kann auch ohne das mir in Delphi ADO angeboten wird, über COM-Objekte zugreifen? Wenn ja wie?<BR>Also kurz gesagt, wie kann ich das oben beschrieben Problem für mich am einfachsten lösen (mit meinen beschränkten Mitteln)
Comment
-
Hallo,<P>momentan lautet mein Source so:<P>
Ich ermittle die in TList abgespeicherten Datensätze, schreibe diese Anzahl in einen Stream um anschließend in einer while-Schleife die Records in den Stream zu schreiben.<P>Das ist nicht gerade ein elegante Möglichkeit, aber die einzige die mir im Moment als möglich erscheint. Ein weiters Problem ist, dass das Record sehr speicherfressend ist (Name: string[30]), d. h. egal wie lange der Name ist, es werden im 30 Byte in den Stream geschrieben.<P>Also das Problem nochmal, wie kann ich mit Delphi 5.0 Standart eine kleine Datenbank entwickeln
Comment
-
Hallo,
man muss nicht unbedingt die <b>ADO Express</b>-Komponenten besitzten, um ADO einsetzen zu können. Das folgende Beispiel demonstriert, wie der Inhalt einer dBASE-Tabelle in einem <b>TStringGrid</b> (kein TDBGrid) angezeigt wird:
<pre>
uses ComObj;
const
cSELECT = 'SELECT * FROM clients.dbf';
cDSN = 'ADODBDEMOSdBASE';
procedure TFormMain.ToolButtonOpenClick(Sender: TObject);
var
vRecordSet : OleVariant;
iRow, iCol : Integer;
begin
vRecordSet := CreateOleObject('ADODB.Recordset');
vRecordSet.Open(cSELECT, cDSN);
iRow := 1;
repeat
for iCol := 0 to 6 do
StringGrid1.Cells[iCol, iRow] := vRecordSet.Fields[iCol].Value;
vRecordSet.Move(1);
Inc(iRow);
until vRecordSet.EOF;
end;
</pre>
Anstelle des ODBC-DSN <i>ADODBDEMOSdBASE</i> (Systemsteuerung | ODBC32-Icon | neuen ODBC-DSN für die eigene Datenbank anlegen) könnte man auch einen anderen ConnectionString für eine andere Datenbank verwenden. Zum Beispiel erlaubt die Zeichenkette "<i>Provider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\Database\dbdemos.mdb;Persist Security Info=False</i>" den Zugriff auf die Delphi-Beispieldatenbank <i>dbdemos.mdb</i>.
Neben dem Auslesen von Daten ist auch das Updaten/Einfügen von Daten möglich. Das folgende Beispiel demonstriert, wie ein Wert geändert werden kann. Im Gegensatz zum ersten Beispiel greife ich hier auf die <i>frühe Bindung</i> (_Recordset-Interface) zurück, damit die Programmier-Hilfe von Delphi 5 auch für die ADO-Objekte von Microsoft zur Verfügung steht. Dazu muss über <i>Projekt | Typbibliothek importieren</i> die ADO-Typbibliothek einmalig in Delphi importiert und die von Delphi automatisch generierte <b>*_TLB.pas</b>-Datei in die eigene Uses-Aufzählung aufgenommen werden - danach kennt Delphi alle ADO-Objekte:
<pre>
procedure TFormMain.ToolButton2Click(Sender: TObject);
var
aRecordSet : _Recordset;
begin
aRecordSet := CoRecordset.Create;
aRecordSet.Open(cSELECT, cDSN, adOpenForwardOnly, adLockOptimistic, adOpenForwardOnly);
aRecordSet.Move(StringGrid1.Row - 1, EmptyParam);
aRecordSet.Fields[StringGrid1.Col].Value := EditNewValue.Text;
aRecordSet.Update(EmptyParam, EmptyParam);
end;
procedure TFormMain.StringGrid1SelectCell(Sender: TObject; ACol,
ARow: Integer; var CanSelect: Boolean);
begin
EditNewValue.Text := StringGrid1.Cells[ACOl, ARow];
end;
</pre>
Es muss dabei nicht unbedingt eine dBASE- bzw. ACCESS-Datenbank sein (auch wenn diese problemlos neu angelegt/bearbeitet werden können). Mann kann auch direkt eine Datendatei bzw. eine XML-Datei als Speicherort verwenden.
Das <b>MDAC-SDK</b> inklusiver der Microsoft-Dokumentation der ADO-Objekte kann von der Microsoft-Webseite <i>http://www.microsoft.com/data/download.htm#26info</i> heruntergeladen werden
Comment
-
Hallo Andreas,<P> ich habe mich am Wochenende ADO durchgearbeitet. Funktioniert sehr schön. Dachte, dass so was mit Delphi 5 Std nicht möglich ist - aber der Mensch irrt. Viel Fragen konnte ich im Ordner ADO bereits finden. Nur eine Frage habe ich noch. Egal was ich mache, eine neuen ODBC-DSN anlegen funktioniert nicht (oder ich stell mich nur doof an). Kannst du ggf. die einzelnen Schritte nochmals erklären (Systemsteuerung | ODBC32-Icon ...).<P> Danke
Comment
-
Hallo,
ein ODBC-DSN ist nur ein Weg von vielen, aber nicht immer der Eleganteste. Alternativ dazu kann direkt ein <i>ADO-ConnectionString</i> verwendet werden, zumal dafür sogar ein Konfigurationsdialog auf jedem Rechner aufgerufen werden kann:
<b>a) Direkter Weg</b>
1. Windows-Explorer: Über <i>Datei | Neu</i> neue Textdatei mit dem Dateinamen <i>Test.txt</i> anlegen. <br>
2. Windows-Explorer: Die Datei nach <i>Test.udl</i> umbenennen. <br>
3. Windows-Explorer: Doppelklick auf <i>Test.udl</i> - der Microsoft-Konfigurations-Dialog <b>Datenverknüpfungseigenschafen</b> wird angezeigt. Hier kann man sich auf den verschiedenen Registerseiten die Verbindungselemente zusammensuchen und die Verbindung testen. <br>
4. War das erfolgreich, die UDL-Datei als Textdatei betrachten:
<pre>
[oledb]
; Everything after this line is an OLE DB initstring
Provider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\Database\dbdemos.mdb;Persist Security Info=False
</pre>
5. Über die Zwischenablage diesen ConnectionString kopieren und im eignen Programm verwenden.
<b>b) ODBC-DSN</b>
- Systemsteuerung | Datenquellen (ODBC) <br>
- Benutzer-DSN | Hinzufügen | Treiber auswählen<br>
- Datenquellennamen frei wählen <br>
- Datenbank über Datei öffnen-Dialog auswähle
Comment
-
Hallo,<br>damit ich eine Liste speichern kann, verwende ich TCollection und TCollectionItem anstatt TList. <br> Unit2 zeigt wie es geht:<br>
<pre><font size="1" face="Verdana">
unit Unit2;
interface
Uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls, ComObj,ActiveX;
Type
TWvgObject = Class(TPersistent)
private
FX : Integer;
FStr : String;
public
procedure Assign(Source : TPersistent); override;
published
property StrText : String read FStr write FStr;
property X : Integer read FX write FX;
end;
TWvgItem = class(TCollectionItem)
private
FDate : TDateTime;
FText : String;
FWvgObject : TWvgObject;
public
constructor Create(Collection: TCollection); override;
destructor Destroy; override;
procedure Assign(Source : TPersistent); override;
published
property Date : TDateTime read FDate write FDate;
property Text : String read FText write FText;
property WvgObject : TWvgObject read FWvgObject write FWvgObject;
end;
TWvgItems = class(TCollection)
private
function GetItem(Index: Integer): TWvgItem;
procedure SetItem(Index: Integer; const Value: TWvgItem);
public
constructor Create;
function Add : TWvgItem;
property Items[Index : Integer] : TWvgItem read GetItem write SetItem;
end;
TStreamDummy = class(TComponent)
private
FItems : TWvgItems;
public
constructor Create(AOwner : TComponent); override;
destructor Destroy; override;
published
property Items : TWvgItems read FItems write FItems;
end;
implementation
{ TWvgItem }
procedure TWvgItem.Assign(Source: TPersistent);
begin
If Source is TWvgItem then
begin
FDate:=TWvgItem(Source).Date;
FText:=TWvgItem(Source).Text;
FWvgObject.Assign(TWvgItem(Source).WvgObject);
end
else
inherited Assign(Source);
end;
constructor TWvgItem.Create(Collection: TCollection);
begin
inherited Create(Collection);
FWvgObject:=TWvgObject.Create;
end;
destructor TWvgItem.Destroy;
begin
FWvgObject.Free;
inherited;
end;
{ TWvgItems }
function TWvgItems.Add: TWvgItem;
begin
Result:=inherited Add as TWvgItem;
end;
constructor TWvgItems.Create;
begin
inherited Create(TWvgItem);
end;
function TWvgItems.GetItem(Index: Integer): TWvgItem;
begin
Result:=inherited GetItem(Index) as TWvgItem;
end;
procedure TWvgItems.SetItem(Index: Integer; const Value: TWvgItem);
begin
inherited SetItem(Index,Value);
end;
{ TStreamDummy }
constructor TStreamDummy.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
FItems:=TWvgItems.Create;
end;
destructor TStreamDummy.Destroy;
begin
FItems.Free;
inherited Destroy;
end;
{ TWvgObject }
procedure TWvgObject.Assign(Source: TPersistent);
begin
If Source is TWvgObject then
begin
FStr:=TWvgObject(Source).StrText;
FX:=TWvgObject(Source).X;
end
else
inherited Assign(Source);
end;
end.
</font></pre><br>TWvgObject soll zeigen, das einem TCollectionItem noch ein Object anhängen kann. TWvgObject wird automatisch gespeichert
Comment
-
Bei Klasse TStreamDummy handelt es sich lediglich um eine Hilfsklasse.
Dadurch, das Items in TStreamDummy published ist, wird die Collection beim schreiben in einen Stream mit geschrieben. Für das Speichern und Laden aus einem Stream verwende ich das IStorage Interface.<br>
<pre><font size="1" face="Verdana">
function LoadAnalyseFromFile(aAnalyse : TTPAnalyse; const Filename : String) : TTPAnalyse;
var
aFilename : WideString;
FRootStorage : IStorage;
stm : IStream;
OS : TOleStream;
StreamDummy : TTPAnalyseDummy;
begin
Result:=aAnalyse;
If Result=Nil then
Result:=TTPAnalyse.Create;
aFilename:=Filename;
If Not Succeeded(StgOpenStorage(PWideChar(aFilename),
nil,
STGM_READWRITE or
STGM_SHARE_EXCLUSIVE,
nil,
0,
FRootStorage)) then
raise EFilenameError.Create('Konnte '+Filename+' nicht öffnen');
OleCheck(FRootStorage.OpenStream(ANALYSEDATA,nil,
STGM_READ or
STGM_SHARE_EXCLUSIVE,
0,stm));
Result.Clear;
StreamDummy:=TTPAnalyseDummy.Create(Nil);
Try
OS:=TOleStream.Create(stm);
Try
OS.ReadComponent(StreamDummy);
Result.Assign(StreamDummy);
Finally
OS.Free;
end;
Finally
StreamDummy.Free;
end;
end;</font></pre>
<br>Die function LoadAnalyseFromFile zeigt, wie man eine Collection aus dem Stream wieder lädt. Ok. die Klassennamen sind etwas unterschiedlich. Aber ich hoffe, dass stört nicht.<br>
<pre><font size="1" face="Verdana">
procedure SaveAnalyseToFile(aAnalyse : TTPAnalyse; const Filename : String);
var
FRootStorage : IStorage;
aFilename : WideString;
stm : IStream;
OS : TOleStream;
StreamDummy : TTPAnalyseDummy;
begin
aFilename:=Filename; // Für Typecasting
OleCheck(StgCreateDocFile(PWideChar(aFilename),
STGM_CREATE or
STGM_READWRITE or
STGM_SHARE_EXCLUSIVE,
0,
FRootStorage));
OleCheck(FRootStorage.CreateStream(ANALYSEDATA,
STGM_CREATE or
STGM_WRITE or
STGM_SHARE_EXCLUSIVE,
0,0,stm));
StreamDummy:=TTPAnalyseDummy.Create(Nil);
Try
StreamDummy.Assign(aAnalyse);
OS:=TOleStream.Create(stm);
Try
OS.WriteComponent(StreamDummy);
Finally
OS.Free;
end;
Finally
StreamDummy.Free;
end;
end;</font></pre><br>SaveAnalyseToFile zeigt wie die Sache gespeichert wird. :-) Jens Schuman
Comment
-
Hallo Jens, <p> habe bereits das ganze mit ADO (so wie Andreas es vorgeschlagen hat) realisiert. Finde Deinen Beitrag super, denn viele haben noch Probleme mit TCollection und TCollectionItem.<br>Laut meinem Wissen befindet sich noch kein so gutes Beispiel hier im Forum. <br>Hilft bestimmt vielen weiter. Danke dafür
Comment
-
Hallo Elmar,<br>vielen Dank. <br>Das interessante an TCollection und TCollectionItem ist, das jedes TCollectionItem wieder eine TCollection TCollectionItem Kombination enthalten kann. Auf diese Weise kann man sich die komplexesten Baumstrukturen ausdenken, die dann einfach komplett gespeichert wird. Ohne das man sich darüber gedanken machen muß. Ich denke, wenn ein Listenelement eine Liste enthalten kann, ist das speichern in eine Datenbank etwas komplizierter<br>:-) Jens Schuman
Comment
Comment