Entwickler-Ecke

Delphi Language (Object-Pascal) / CLX - RECORDS - PROBLEM BEI SPEICHERN ODER LESEN?


Merkur - So 16.02.14 17:07
Titel: RECORDS - PROBLEM BEI SPEICHERN ODER LESEN?
Delphi 7 und Delphi 2009 (bei beiden Vers. gl. Problem)
GUTEN TACH ZUSAMMEN!

Nachtrag: Möchte noch hinzufügen, dass ich nicht glaube, dass beide Delphi-Vers. fehlerhaft sind!
Ich gehe davon aus, dass ich grundsätzlich was falsch mache!?

Eigentl. Traue ich mich kaum noch, mein Problem im Forum zu schildern.
Es ist wahrsch. simpel, das Probl. zu lösen aber aufwendig es zu schildern! SORRY!
Ich habe in den letzten Wochen an die 50 versch. InternetSeiten besucht, um mich möglichst genau
über die Verwendung/Deklaration von Records zu informieren.
3 Bücher habe ich noch: For Kids, Delphi in Team, Delphi.Net- Fehlanzeige!
Die meisten Beschreibungen ähneln sich, was immer ich eingesetzt habe, ich komme nicht ans Ziel!
Meine Vorgehensweise(weiter unten) habe ich von http://www.delphi-treff.de übernommen.

Ich lese per code eine Textdatei ein, die Läden(Geschäfte) mit den zugehörigen Orten(Filialen)
enthält. Da ich mit diesen Daten Einkäufe verwalten will, benötige ich das Ganze in Form von Records.
Was Wo Wann für wieviel Geld gekauft?
Die Läden liste ich zur Zeit in einer Listbox, die Orte dazu in einer Checklistbox daneben.
Klicke ich auf einen Laden, werden daneben die zugeh. Filialen gelistet, klappt soweit sehr gut,per readln, ohne(!) Record.
Alles, allein ohne Records klappt bestens, spätestens wenn ich Geldbeträge speichern will, sollte das aber mit records sein.

Nur kriege ich die Daten nicht fehlerfrei in Records gespeichert ODER ich kriege sie nicht fehlerfrei eingelesen!?
Es erscheinen bei mind.95% Hyroglyphen/Datenmüll/Ascii/Sonderzeichen, wenn ich die gespeicherten Records liste.
Von 30 Records zeigt manchmal einer einen richtigen Laden-Name bzw. Strassen-Name.
Für Einkäufe habe ich separat einen Record erstellt(Bei dem hab ich dieselben Probleme wie bei TLaden)

Nachfolgend schreibe ich die Daten der Textdatei in den Record TLaden:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
procedure TForm1.FormCreate(Sender: TObject);
type TLaden=record
     Index: integer; 
     Check: byte;
      Name: string[20];
     Datum: array[1..3of string[10];
       Ort: array[1..5of string[20]; // Max. 5 Filialen/Strassen je Laden
       Typ: string[20];
       end;
Var i,j: integer;
      s: string;
     sz: string[2]; // steht für: string-zahl, nimmt Index(2-stellige Zahl vorne und hinten) der Textdatei auf
      F: Textfile;
      L: File of TLaden;
  Laden: array[1..40of TLaden;
begin assignfile(F,'D:\FIL.TXT'); // Textdatei mit Läden
assignfile(L,'D:\FIL.DAT');       // Record-Datei
reset(F); rewrite(L);
i:=0;
while not eof(F) do
      begin
      readln(F,s);
      if pos('_',s)>0 then // Die Stringbehandlung evtl. etwas altmodisch, habs noch von TP :roll: 
         begin 
         i:=i+1;  
         j:=0;   // LadenName gefunden: Strassen-Zähler <j> auf Null
         Laden[i].Name:=s;
         sz:=copy(s,1,2); // Laden-Index (Zahl vorne in Textdatei)
         Laden[i].Index:=strtoint(sz);
         sz:=copy(s,length(s)-1,2); // Strassen-Index (Zahl hinten in Textdatei)
         Laden[i].Check:=strtoint(sz);
         Laden[i].Datum[1]:=DateToStr(Date); // Laden Erstellt
         Laden[i].Datum[2]:='Datum2';        // Zum Testen 'Datum2'
         Laden[i].Datum[3]:='Datum3';        // Zum Testen 'Datum3'
         repeat j:=j+1;  // Strassen einlesen
                readln(F,s);
                if pos('#',s)=0 then   // Solange Laden-Typ nicht erreicht, ist es eine Strasse
                   Laden[i].Ort[j]:=s; // hier befindet sich zwar noch der Index am Anfang der Zeile, aber erstmal egal.
                   until pos('#',s)>0// Laden-Typ erreicht, Strassen(Laden) zu Ende
         Laden[i].Typ:=s;
         write(L,Laden[i]);
         end;
      end;
closefile(F);
closefile(L);
end;


Hiermit lese ich die Records(wieder in FormCreate) ein (eigens dafür kl. Prog zum Testen)
Die Daten darin sollen beim Progstart in den Listboxen gelistet werden!


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
procedure TForm1.FormCreate(Sender: TObject);
type TLaden=record
     Index: integer;
     Check: byte;
      Name: string[20];
     Datum: array[1..3of string[10];
       Ort: array[1..5of string[20];
       Typ: string[20];
       end;

Var anz,i,j,Li: integer; //anz=Anzahl(Läden), i:Zähler(Laden) j:Zähler(Strassen), Li:Listbox.ItemIndex
             L: File of TLaden;
         Laden: array[1..40of TLaden;
        // Die folgenden Vars mit '_' habe ich global dekl., da ich auch im ListboxOnClick drauf zugreife.
        _check: array[1..40of byte;            // Focus/Index für Strassen(Checklistbox)
         _name: array[1..40of string[20];      // Laden
          _ort: array[1..40,1..5of string[20]; // Orte
begin
for i:=1 to 40 do // Globale Vars initialisieren
    begin
    _check[i]:=0;
    _name[i]:='';
    for j:=1 to 5 do
        _ort[i,j]:='';
    end;
assignfile(L,'D:\FIL.DAT');
reset(L);
anz:=0; i:=0;
while not eof(L) do
      begin j:=0;
      i:=i+1;
      anz:=i;
      read(L,Laden[i]);
      _check[i]:=Laden[i].Check;
      _name[i]:=copy(Laden[i].Name,4,length(Laden[i].Name)-5);
      repeat j:=j+1;
             if _Check[i]>=10 then // Dann war diese Strasse zuletzt bearbeit worden
                begin
                Li:=i; // Weiter unten den betr. Laden und die Strasse markieren(Focus drauf)
                if Laden[i].Ort[j]<>'' then
                   _ort[i,j]:=copy(Laden[i].Ort[j],3,length(Laden[i].Ort[j])-2);
                end;
      until Laden[i].Ort[j]='';
      end;
closefile(L);

Listbox1.Clear;
for i:=1 to anz do
    Listbox1.Items.Add(_name[i]);
Listbox1.ItemIndex:=Li-1;

Checklistbox1.Clear;
j:=0;
repeat inc(j);
       CheckListbox1.Items.Add(_Ort[Li,j]);
       until _Ort[Li,j]='';
CheckListbox1.ItemIndex:=(_Check[Li-1]-10);
end;


Hier ein Auszug aus der Textdatei:
ACHTUNG! Die Kommentarzeilen sind in der Textdatei sonst nicht vorhanden!

00_Agip00 // Zahl am Ende der Zeile, welcher Ort(nächte Zeile(n) zuletzt bearbeitet?
00Dietzgenstrasse // Index=2-stellige Zahl am Anfang der Zeile(Bei Laden UND zugehöriger Strasse IMMER GLEICH)
#TANKSTELLE // Die Zeile TYPimmer mit '#', hiernach folgt immer der nächste Laden!
01_Aldi01 // Laden-Name immer mit '_', hiernach folgen Strasse(n) zu dem Laden
01Alt-Rudow // Strassen beginnend mit Null
01Stubenrauchstr. // Beispiel hier: Aldi mit Strasse(01)= Stubenrauchstr. -
#DISCOUNTER


Ich entschuldige mich für das viele Geschreibe!
UND HOFFE AUF EURE FREUNDLICHE HILFE!!!!
VIELEN DANK IM VORAUS!!! Gruß Uli


Narses - So 16.02.14 17:20

Moin und :welcome: in der EE!

Kannst du bitte mal dein Demo-Projekt (am besten die D7-Version) in ein ZIP-Archiv packen und hier reinstellen (du brauchst nur die .PAS, .DFM und .DPR-Dateien, sowie die Demo-Text-Liste, compilieren können wir das ja selbst). Ist einfacher sich das live anzusehen. ;)

cu
Narses


jaenicke - So 16.02.14 17:37

Hallo und :welcome:

Wenn das wirklich ein echtes Programm werden soll und nicht nur für Schule oder ähnliches zum Üben, macht ein file of mit einem Record absolut keinen Sinn für diesen Zweck.

Dafür nimmt man eine Datenbank und kann damit bequem arbeiten. Dann brauchst du auch nicht ShortStrings usw. sondern kannst einfach mit normalen Strings arbeiten usw.
Noch dazu hat man dann kein proprietäres Format, sondern man kann das auch aus anderen Programmen heraus bei Bedarf einlesen.

Bei mir funktioniert dein Code jedenfalls prinzipiell schon, aber es ist nirgends sichergestellt, dass auch z.B. alle Orte in dem Record initialisiert sind. Da kann daher auch Blödsinn drin stehen.
Aber wie gesagt, das Problem hast du bei einer Datenbank gar nicht erst.


Delete - So 16.02.14 17:40

Grundsätzlich ist es falsch, das alles in FormCreate auszuführen.
Vieles existiert noch garnicht, auf das Du zugreifst!


Narses - So 16.02.14 17:50

Moin!

Leute, ihr seid sicher hilfsbereit und kompetent :beer: aber holt den Mann doch da ab, wo er steht. Jemand der sich mit einer DB auskennt, kommt nicht mit file of records an, soviel kann man sich denken. Und wenn man damit noch nix zu tun hatte, springt man auch nicht mal eben in die DB-Ansteuerung rein. :roll:

cu
Narses


Merkur - So 16.02.14 18:08

DANKE FÜR DIE SCHNELLEN ANTWORTEN!

Zunächst, den Code von Delphi 7 habe ich nicht mehr, wollte da nicht weitermachen.

Eine Datenbank habe ich noch nie programmiert.
Das Prog sollte für mich sein, um zu sehn wo mein Geld bleibt und gleichzeitig etwas beim programmieren zu lernen.

Wenn das wirklich ein echtes Programm werden soll und nicht nur für Schule oder ähnliches zum Üben, macht ein file of mit einem Record absolut keinen Sinn für diesen Zweck.
Das habe ich schon mal gelesen, nur ist mir nicht klar, warum es so auf delphi-treff zu lesen ist??

Das FormCreate hier problematisch sein könnte, kann ich nachvollziehen.
Ich werde die Routine noch versuchsweise in eine ButtonClick-Procedure verschieben.

Zu Narses:
Gut beobachtet!
Kann nicht mal eben in die DB-Ansteuerung rein.

Vermutlich sollte ich das Programmieren aufgeben.
Habe soviel über dieses Problem nachgelesen und geschrieben, ich komme auf keinen grünen Zweig.
Delphi ist nicht TP. Und von Grund auf alles lernen, naja, ich mit meinen 57 J.
Leider gibt es keine Schulkurse für Delphi.. (Mir jedenfalls nicht bekannt) Würde gern dafür bezahlen.
DANKE EUCH VIELMALS!!!


mandras - So 16.02.14 19:36

Na das mit dem Programmieren aufhören laß mal :)

Ich habe Dein Programm nicht übersetzt, sondern nur einmal die Codezeilen verglichen, dort fiel mir auch nicht besonderes auf.
Was sein könnte (wenn auch unwahrscheinlich): Die Compilereinstellungen beider Anwendungen könnten unterschiedlich sein, was dann zu unterschiedlichem Recordaufbau führt.
Versuche einmal

type TLaden=packed record

in beiden Programmen.

Begründung: Es gibt die Projekteinstellung "Ausrichtung von Recordfeldern". Wenn diese Einstellung bei beiden Projektdateien unterschiedlich sind ergeben sich auch verschieden aufgebaute Records - Der Compiler macht das, damit die einzelnen Felder nach Möglichkeit so im Speicher stehen, daß der Prozessor/Cache ohne großen Zeitverlust darauf zugreifen können. Dies passiert dann, wenn die Felder (je nach Prozessor) an Adressen stehen, die glatt durch 4/8/16 erc teilbar sind.

Alternativ könntest Du dir bei beiden Programmen einmal die Größe der Records mittels sizeof (TLaden) anzeigen lassen. Sie müssen gleich sein, sonst gibt es Probleme.


WasWeißDennIch - Mo 17.02.14 09:16

Packed oder nicht spielt doch beim Einlesen einer Textdatei keine große Rolle, bei einer typisierten Datei sähe das anders aus.


jasocul - Mo 17.02.14 10:18

user profile iconWasWeißDennIch hat folgendes geschrieben Zum zitierten Posting springen:
Packed oder nicht spielt doch beim Einlesen einer Textdatei keine große Rolle, bei einer typisierten Datei sähe das anders aus.

Es wird mit zwei Dateien gearbeitet. Eine Text-Datei und eine typisierte Datei. Die typisierte wird im ersten Source erzeugt und im zweiten eingelesen. Das Problem mit packed Record ist also naheliegend und würde genau zu den beschriebenen Problemen passen.

Zum besseren Verständnis [http://www.delphibasics.co.uk/RTL.asp?Name=$Align]


Merkur - Mo 03.03.14 22:13

Hallo Delphi-Freunde!!!

Zum o.g. Record-Problem möchte ich meine Erfahrungen kurz mitteilen:

Habe es bisher mit folgenden Versionen probiert:
Delphi 7 pers.
Delphi 2009
Delphi XE5(Trial Vers.)

ALLE! Aber auch ALLE Delphi-Vers. verhalten sich gleich-
Immer wieder werden die Daten der Records mit Daten-Müll vermischt(Eben Nur die Arrays).

Bisher in etwa so gemacht:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
    type TLaden=Record
         Name: string[20];
         Orte: array[1..5of string[20];
         Typ: string[20];
      Betrag: array[1..4of single;
         Summe: single;
         Check: byte;
         end;


Das Einzige, dass jetzt tadelos funktioniert, ist der Record OHNE Arrays!!
So klappt es endlich:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
    type TLaden=Record
         Name: string[20];
         Ort1: string[20];
         Ort2: string[20];
         Ort3: string[20];
         Ort4: string[20];
         Ort5: string[20];
      Betrag1: single;
      Betrag2: single;
      Betrag3: single;
      Betrag4: single;
         Typ: string[20];
         Summe: single;
         Check: byte;
         end;


Es ist unsauber UND unschön UND umständlich :?
Da ich das Prog nur für mich nutzen will und es SO ENDLICH KANN,
muss und will ich damit aber leben.

Es ist und bleibt mir unverständlich, dass bei all meinen Nachforschungen(in mehreren Foren)
dieses Problem nicht in den Griff zu kriegen ist!

Habe dann auch alles außerhalb von Form.Create getestet- NICHTS!
Auch mit packed Record kam ich nicht ans Ziel(genau dasselbe Problem)!

Die Delphi-Bücher, die ich habe, "Delphi 6 in Team" "Delphi.Net" gehen mit keinem Wort über dieses Problem ein!

Für mich sind Records einfach unverzichtbar!
Es wäre PERFEKT, diese mit Arrays nutzen zu können! :roll:

Könnte denn jemand von Euch ein wirklich brauchbares Buch empfehlen?
Gruß Uli


jaenicke - Mo 03.03.14 23:37

Bei mir funktioniert das Speichern und Laden des Records problemlos. Ohne ein vollständiges Beispielprogramm kann ich dein Problem leider nicht nachvollziehen.

Hier ein vollständiges Beispiel, das einfach Demodaten erzeugt:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
type
  TLaden = record
    Name: string[20];
    Orte: array [1 .. 5of string[20];
    Typ: string[20];
    Betrag: array [1 .. 4of single;
    Summe: single;
    Check: byte;
  end;

//...

procedure TForm67.btnSaveClick(Sender: TObject);
var
  a: file of TLaden;
  Data: TLaden;
  i: Integer;
begin
  AssignFile(a, 'test.data');
  Reset(a);
  for i := 1 to 10 do
  begin
    Data.Name := 'Name' + IntToStr(i);
    Data.Orte[1] := 'Orte1' + IntToStr(i);
    Data.Orte[2] := 'Orte2' + IntToStr(i);
    Data.Orte[3] := 'Orte3' + IntToStr(i);
    Data.Orte[4] := 'Orte4' + IntToStr(i);
    Data.Orte[5] := 'Orte5' + IntToStr(i);
    Data.Typ := 'Name' + IntToStr(i);
    Data.Betrag[1] := i;
    Data.Betrag[2] := 2*i;
    Data.Betrag[3] := 3*i;
    Data.Betrag[4] := 4*i;
    Data.Summe := 100*i;
    Data.Check := 10*i;
    Write(a, Data);
  end;
  CloseFile(a);
end;

procedure TForm67.btnLoadClick(Sender: TObject);
var
  a: file of TLaden;
  Data: TLaden;
  i: Integer;
begin
  AssignFile(a, 'test.data');
  Reset(a);
  for i := 1 to 10 do
  begin
    Read(a, Data);
    ShowMessage(Data.Name);
    ShowMessage(Data.Orte[1]);
  end;
  CloseFile(a);
end;


mandras - Di 04.03.14 00:01

> Es ist und bleibt mir unverständlich, dass bei all meinen Nachforschungen(in mehreren Foren)
> dieses Problem nicht in den Griff zu kriegen ist!

Ich bin mir sicher das wäre es, wenn Du uns mehr Informationen zukommen lassen würdest.

Wie jaenicke schon schrieb, ein Beispielprogramm Deinserseits wäre hilfreich.

Es ist ja verständlich wenn Du sauer bist daß das Problem noch nicht gelöst ist, aber bitte weniger Ausrufezeichen :)

Ein Buch welches sich genau mit dieser Problematik ausführlich beschäftigt kenne ich nicht.

Bei meinen Arbeiten habe ich auch schon vereinzelt derartiges programmiert, wenn ich die records als packed definierte klappte auch alles.
Die nicht benutzten Bereiche des records enthalten dann gerne Unsinn, das wirkt sich auf die Programmfunktion aber nicht aus.
Aus Sicherheitsgründen (damit in den Dateien nichts steht was niemand lesen soll) empfiehlt es sich den ganzen Record im Zweifelsfall vorher zB mit Nullen zu füllen.


tempuss - Do 06.03.14 13:28

Hallo mandras!

Nein, auf die Programmfunktion wirkt sich der angezeigte 'Datenmüll' nicht aus.
Es ist einfach unschön und sinnlos, wenn zwischen den richtigen Daten auch noch Datenschrott
in der Listbox erscheint.
Ich frage vor dem Listen ab, ob die betr. Variable <>'' ist, das ist natürlich auch der Fall bei
zufälligen ASCII-Zeichen.

Ich werde jetzt vorher auch noch die Records initialisieren, alles auf '0' oder ' '.

DANKE EUCH!!!


Sinspin - Fr 07.03.14 10:57

Hi, mit was für Sprachen/Werkzeugen hast Du bisher programmiert? Bzw. wieviele Jahre Erfahrung im programmieren hast Du?

Da die Records immer eine feste Größe haben schreibt man automatisch Müll mit in die Datei. Nämlich die Bereiche des Speichers an denen keine sinnvollen Daten stehen. Aber die solltest Du eigentlich nach dem Auslesen nicht zu Gesicht bekommen.

Eine Listbox merkt sich ihre Daten in einem Objekt namens TStings, dieses verfügt über Methoden (Proceduren und Funktionen) zum Schreiben und Lesen von Textdateien. Das wäre schonmal eine Erleichterung.
Dann gibt es das CSV Format, welches TStrings "versteht". Damit kann man alle Einträge (Zeilen) einer Stringliste (TStringList, nachkomme von TStrings) via der Eigenschaft CommaText als einen CSV formatierten String auslesen und schreiben. So könntest Du einen von Deinen Records / Datensatz als einen String auffassen. Schreibst Du die einzelnen Datensätze wieder in eine TStringList kannst du die Daten locker lesen und schreiben. Und hast so eine ganz einfache Datenbank (bitte schlagt mich nicht für diesen Vergleich ;-)) die sogar kleiner ist als Deine File of record da Du keinen Müll mit schreibst.

Noch etwas ganz wichtiges, bitte, schrei hier nicht die ganze Zeit rum (Worte komplett groß schreiben) wir werden davon ganz taub und blind.


jasocul - Fr 07.03.14 14:28

Ich habe mir das jetzt mal genauer angesehen. Die Ursache liegt meiner Meinung nach in diesem Code-Fragment:

Delphi-Quelltext
1:
2:
3:
4:
5:
repeat j:=j+1;  // Strassen einlesen
  readln(F,s);
  if pos('#',s)=0 then   // Solange Laden-Typ nicht erreicht, ist es eine Strasse
    Laden[i].Ort[j]:=s; // hier befindet sich zwar noch der Index am Anfang der Zeile, aber erstmal egal.
until pos('#',s)>0// Laden-Typ erreicht, Strassen(Laden) zu Ende

Es werden nicht alle Orte in dem Array gefüllt, wenn j nicht bis 5 läuft.
Wenn dann die Daten aus dem File of Record eingelesen werden, steht in den nicht gefüllten einfach Müll. Das ist normal. Wenn man dann versucht die Inhalte zu verarbeiten, ohne prüfen zu können, ob da Inhalt sein darf, kann nur Müll angezeigt werden. Also entweder das Orts-Array richtig initialisieren oder ein Kriterium speichern, wieviele Orte überhaupt zulässig sind.