Avatar billede jonat Nybegynder
02. august 2006 - 22:55 Der er 9 kommentarer

Gemme Records i en "File of"

Hej.

Jeg sidder og leger lidt med et tidsregistrerings program.

Den skal gemme en listbox's indhold i en "file of" et record.


type
  TProjektData = Record
    ProjektNavn : String[255];
    Dato : TString;
    StartTid : TStrings;
    SlutTid : TStrings;
    MillisekunderArbejdet : TStrings;
    Beskrivelse : TStrings;
end;


Jeg har 2 procedurer, gemme og åbne projektfil.

Den giver hverken fejlmeddelser når den åbner eller gemmer filen.

Men når jeg har gemt filen og åbnet den igen, er der ingen indhold i listbox'ene. Men navnet på projektet henter den fint "ProjektNavn : String[255];".

Nogen der kan fortælle mig hvad jeg gør galt, kan bare ikke forstå det.

Mvh.
Jonatan

På Forhånd Tak :)

**** KODE START ****

**** GEM ****
procedure TForm1.GemDatafil1Click(Sender: TObject); 
var
  Fil : File of TProjektData;
begin
  If not ProjektEandretSidenGem then
    exit;

  If SaveDialog1.Execute then
  begin
    If ExtractFileExt(SaveDialog1.FileName) <> '.jhc' then
    begin
      SaveDialog1.FileName := SaveDialog1.FileName + '.jhc';
    end;

    If FileExists(SaveDialog1.FileName) then
    begin
      If MessageDlg('Vil du erstatte projektfilen: '+ExtractFileName(SaveDialog1.FileName),mtWarning,mbOKCancel,0) = MrCancel then
        Exit;
    end;

    AssignFile(Fil, SaveDialog1.FileName);
    ReWrite(Fil);

    AktuelProjekt.ProjektNavn          := EdtProjekt.Text;
    AktuelProjekt.Dato                  := LbDato.Items;
    AktuelProjekt.StartTid              := LbStart.Items;
    AktuelProjekt.SlutTid              := LbSlut.Items;
    AktuelProjekt.MillisekunderArbejdet := LbMillisekunder.Items;
    AktuelProjekt.Beskrivelse          := LbBeskrivelse.Items;

    Write(Fil, AktuelProjekt);

    CloseFile(Fil);
    ProjektEandretSidenGem := False;
  end;
end;

**** GEM SLUT ****


**** ÅBEN ****
procedure TForm1.bendatafil1Click(Sender: TObject);
var
  Fil : File of TProjektData;
begin
  If OpenDialog1.Execute then
  begin
    If FileExists(OpenDialog1.FileName) then
    begin
      AssignFile(Fil, OpenDialog1.FileName);
      Reset(Fil);

      Read(Fil, AktuelProjekt);

      CloseFile(Fil);

      LbStart        .Items := AktuelProjekt.StartTid;
      LbDato          .Items := AktuelProjekt.Dato;
      LbSlut          .Items := AktuelProjekt.SlutTid;
      LbMillisekunder .Items := AktuelProjekt.MillisekunderArbejdet;
      LbBeskrivelse  .Items := AktuelProjekt.Beskrivelse;
      EdtProjekt      .Text  := AktuelProjekt.ProjektNavn;

      ShowMessage(AktuelProjekt.StartTid.Text);
      ShowMessage(LbStart.Items.Text);

      ProjektAabnet := True;
      BtnStart.Enabled := True;
    end
    else
    begin
      ShowMessage('Data filen kunne ikke findes.');
    end; 
  end;
end;
**** ÅBEN SLUT ****
**** KODE SLUT ****
Avatar billede erikjacobsen Ekspert
02. august 2006 - 23:21 #1
Fordi du kan gemme en string[255] (og integer og andre basale typer), men ikke TStrings.
Avatar billede a_nor Nybegynder
03. august 2006 - 11:25 #2
Erik har ret i hvad han skriver. De objekter du angiver i recorden har jo blot en "pointer størrelse" og det er denne du får gemt og ikke dataene.

Du er altså nødt til at gemme element for element. Lav recordstrukturen om


type
  TProjektData = Record
    ProjektNavn : String[255];
    Dato : string[12];
    StartTid : String[12];
    SlutTid : TStrings[12];
    MillisekunderArbejdet :string[12] // hva ska du med den ??
    Beskrivelse : string;
end;

og gem så listboxens indhold linie for linie.

(Har iørigt arbejdet med tidsreg. i mange år)
Avatar billede hrc Mester
04. august 2006 - 17:10 #3
Er det noget der skal indlæses i eksempelvis Excel? Hvis det bare er lager så synes jeg det bliver rigtig pænt om du bruger TFileStream sammen med TReader og TWriter. Meget let at gemme og hente - og så bliver det også rimeligt kompakt.

Man skal bare huske FlushBuffer hvis man har "nestede" skrivninger for ellers går det grumme galt.

begin
  fs := TFilestream.Create('c:\test.dat',xxCreate);
  Writer := TWriter.Create(fs,1024);
  try
    Writer.WriteString(AktuelProjekt.ProjektNavn);
    Writer.WriteDateTime(AktuelProjekt.
    Writer.WriteDateTime(AktuelProjekt.StartTid);
    Writer.WriteDateTime(AktuelProjekt.SlutTid);
    Writer.WriteInteger(AktuelProjekt.Millisekunder);
    Writer.WriteString(AktuelProjekt.Beskrivelse);
    Writer.FlushBuffer;
  finally
    Writer.Free;
    fs.Free;
  end;
end;

Tilsvarende når man læser, bare med TReader i stedet.
Avatar billede jonat Nybegynder
06. august 2006 - 22:50 #4
Kan man bare skrive hvad man vil til en flushbuffer?

Er det det man generelt bruger når man laver mere komplicerede filer?

//Jonat...
Avatar billede a_nor Nybegynder
07. august 2006 - 10:17 #5
til hrc:
tak for tippet, interessant og værd at huske på.

MEN: Hvert item i objekterne ( Dato, StartTid, SlutTid, Beskrivelse) skal stadig gemmes separat!
Avatar billede hrc Mester
07. august 2006 - 11:13 #6
Man bruger det til at gemme komponenterne med i Delphi bl.a.

Fordelen er bl.a. at du ikke er bundet af nogen strenglængde. En anden er, at hvis du har en liste med nogle objekter så skal de hver kun sørge for at gemme sig selv - og så er det at FlushBuffer bliver vigtig.

Viser her et lille eksempel på en liste der indeholder en liste af persondata-objekter. Listen skriver sine oplysninger og sender derefter stafetten videre til person-objekterne der kun bekymrer sig om at skrive sit. På den måde kan du bryde opgaven ned i mindre bidder og kompleksiteten bliver meget mindre

Data bliver i øvrigt også skrevet med felterne separeret - men dog optimeret - i en binær-fil.

type
  TPersonData = class
  private
    fNavn : string;
    fAdresse : string;
    fLoen : double;
  public
    constructor Create(const aNavn, aAdresse : string; const aLoen : double);
    procedure LoadFromStream(aStream : TStream);
    procedure SaveToStream(aStream : TStream);
    property Navn : string read fNavn;
    property Adresse : string read fAdresse;
    property Loen : double read fLoen;
  end;

  TPersonList = class(TObjectList)
  private
    fTitel : string;
    function GetPersonData(const aIndex : integer) : TPersonData;
  public
    constructor Create(const aTitel : string); overload; // Hvis du opretter ny liste
    constructor Create(aStream : TStream); overload; // Hvis du opretter fra stream
    procedure LoadFromStream(aStream : TStream); // Kaldes i constructor #2
    procedure SaveToStream(aStream : TStream);
    property Items[const aIndex : integer] : TPersonData read GetPersonData; default;
  end;

// PersonData

procedure TPersonData.SaveToStream(aStream : TStream);
var
  Writer : TWriter;
begin
  Writer := TWriter.Create(aStream, 1024);
  try
    Writer.WriteString(fName);
    Writer.WriteString(fAdresse);
    Writer.WriteFloat(fLoen);
    Writer.FlushBuffer;
  finally
    Writer.Free;
  end;
end;

// PersonList

procedure TPersonList.SaveToStream(aStream : TStream);
var
  Writer : TWriter;
begin
  Writer := TWriter.Create(aStream, 1024);
  try
    Writer.WriteString(fTitle);
    Writer.WriteDate(now);
    Writer.WriteInteger(Count); // Skriv antallet af records
    Writer.FlushBuffer; // Flush!
    for i := 0 to Count - 1 do
      Items[i].SaveToStream(aStream);
  finally
    Writer.Free;
  end;
end;
Avatar billede hrc Mester
07. august 2006 - 11:14 #7
Småprogrammer som Microsoft Office streamer garanteret også kun på den måde
Avatar billede a_nor Nybegynder
07. august 2006 - 11:49 #8
hrc: Endnu engang tak. Det er godt med noget kode at studere.
Hvis man indledningsvis gemmer objektnavnet kan man vel også (ustruktureret) smide objekter ned i filen, og stadig få dem læst ind igen, og du behøvede ikke at gemme 'count' ?
Avatar billede hrc Mester
07. august 2006 - 12:35 #9
Du spottede den count'en - den var jeg noget i tvivl om jeg skulle tage med. Svaret er både og. Man kan nemlig også lave loopet således: "while aStream.Position < aStream.Size" og derved spare Count.

Tror du bliver nødt til at hælde en ClassType/ClassName ned før du fodrer den med et objekt. Når du indlæser skal du have en eller anden måde at finde den rette indlæsningsrutine.

procedure TPersonData.SaveToStream(aStream : TStream);
var
  Writer : TWriter;
begin
  Writer := TWriter.Create(aStream, 1024);
  try
    Writer.WriteXxXX(ClassType); // Sådan her
    Writer.WriteString(fName);
    Writer.WriteString(fAdresse);
    Writer.WriteFloat(fLoen);
    Writer.FlushBuffer;
  finally
    Writer.Free;
  end;
end;

I din indlæsningsloop bliver du nødt til at lave noget i retning af

  case Reader.ReadXxXX of
    ctPersonData : Add(TPersonData.Create(aStream));
    ..
  end;
Avatar billede Ny bruger Nybegynder

Din løsning...

Tilladte BB-code-tags: [b]fed[/b] [i]kursiv[/i] [u]understreget[/u] Web- og emailadresser omdannes automatisk til links. Der sættes "nofollow" på alle links.

Loading billede Opret Preview
Kategori
Kurser inden for grundlæggende programmering

Log ind eller opret profil

Hov!

For at kunne deltage på Computerworld Eksperten skal du være logget ind.

Det er heldigvis nemt at oprette en bruger: Det tager to minutter og du kan vælge at bruge enten e-mail, Facebook eller Google som login.

Du kan også logge ind via nedenstående tjenester