Link to home
Start Free TrialLog in
Avatar of mi6agent
mi6agent

asked on

Records

I am putting a small program together using Delphi 2 to use as a small address book.

I am using records to keep the information on each person together with their telephone number, address, etc and then using Write(File, Rec) to save the information.

i am using a treeview to hold letters from A to Z and i insert a record under each node letter and use the imageindex of the sibling node to hold the record number.

eg:

A
B
.
J
..Jones (imageindex = 2)
.
S
..Smith (imageindex = 1)
.
.
Z

My problem is how can i save the contents of the treeview along with the information i enter as one file so it can be restored when i reload it at a later date.

Keep in mind i have included the ability to add and delete records to the program.

Anybody got any good ideas on how to do this?
Avatar of Mohammed Nasman
Mohammed Nasman
Flag of Palestine, State of image

Hello

  Why u you don't use the database to save and restore ur data?, it's will be better than using files,

MOhammed
Avatar of Jaymol
Jaymol

Hi.

Can't you just recreate the treeview from the data stored in the records file?

John.
Avatar of mi6agent

ASKER

mnasman, i don't want to start learning the database side of delphi 2 as it is only a small program for my own use and i have no examples to learn from.

jaymol, i was thinking along the same lines but i am unable to figure out how best to do it that way.
I would suggest that during the read process, simply pass the information to the procedure that creates the treeview when data is entered.

Do you store the record number (imageindex) in the data file?

John.
jaymol, i don't store the imageindex in the data file.  at the moment i have the treeview setup so i can add/delete nodes and siblings and i have it set up so i can add/delete/edit records BUT i am unable to put the both ideas together and am not able to figure out a good way to do it.

i am willing to increase the points for help in solving this problem.
Is there any way I could see your problem?  I can't quite picture exactly what it is.

John.
Jaymol,

What i am doing is adapting a program i found here:

http://www.delphicorner.f9.co.uk/tips/apps/apps6.htm

What i am doing is:

Added a treeview to the form and code to enter/edit/delete main titles (nodes: such as A..B..C etc) and also add/edit/delete siblings (the actual record title such as Smith, jones etc)  When each sibling is added i record a number in the imageindex of the sibling as i want this to point to the actual reord in the file.

My problem is that i am finding it hard to be able to link the info together in such as way so that i can have only one file instead of saving the records and treeview as different files.

can't figure out what the best way is to link them.

 
You need to save the record number with the data.  Then, when you import the records again, you have enough information to rebuild the treeview.  Is that right?

John.
Change this....

    type
      TAddressRec = record
        First : String[20];
        Last  : String[20];
        Add1  : String[40];
        Add2  : String[40];
        City  : String[30];
        ST    : String[2];
        Zip   : String[10];
        Phone : String[20];
        Fax   : String[20];
      end;

to this....

    type
      TAddressRec = record
        RecNum : Integer;
        First  : String[20];
        Last   : String[20];
        Add1   : String[40];
        Add2   : String[40];
        City   : String[30];
        ST     : String[2];
        Zip    : String[10];
        Phone  : String[20];
        Fax    : String[20];
      end;

and make sure that you put the same number in the RecNum field as you put in the imageindex.

John.
Problem is that the main nodes are not saved so i am unable to rebuild the treeview - if i saved the node information in the reord would that not be a waste of space and cause problems?

what about when i delete a record?  would that not cause things to go wierd with the numbers since i am using those to call up the record when someone click on the sibling?

example, using the program if i create 4 nodes (A, J, S and W) and under A create "ash" it will have the imageindex of 1 since it is the first record created.

then if i enter a sibling (Jones) under the J node it will be 2 and then 3 would be assigned to Smith when i create it under the S node and 4 for Williams under the W.  If i then delete Smith would that not cause problems?
hm, I remember in Pascal it was possible to create a file of record and then when reading the file you could read out the records. Don't know D2 but I think you could use this too to store your data.
then when reading the data you can just create the whole treeview.
first create the nodes with the letters a to z.
then take the first letter of the name you want to insert and compare it with the nodes text to find the right letter(node) something like ...

function TForm1.FindNode(AChar : String; MyNode : TTreeNode) : TTreeNode;
var TempNode : TTreeNode;
begin
  Result := NIL;
  if MyNode.text = AChar then
  begin
    Result := MyNode;
    exit;
  end
  else
  begin
    tempNode := MyNode.GetNext;
    if not (tempNode = NIL) then
      Result := FindNode(AChar, tempNode)
    else
    begin
      tempNode := MyNode.GetNextSibling;
      if not (tempNode = NIL) then
        Result := FindNode(AChar, tempNode);
    end;
  end;
end;

this function returns the node that has the right letter.
then you can add a new child to that letter with the contents off the record.

here's another example

you have already a treeview containing 26 nodes that have NIL as parent and texts from a to z. You read in a record let'S say it is the one with Jones.
You take the first letter J...
var MyParentNode : TTreeNode;
  MyParentNode := FindNode('J', TreeView1.Items.GetFirstNode);
  TreeView1.Items.AddChild(MyParentNode, 'Jones');

Do this for all your records ...

To save the data when changed just go through all you nodes and write the data to a file.
oops, missed out some comments while i was writing mine
to you last comment... just store a counter with your data that will count the number f records entered and if you delete on (for example you entered 3 and then delete the third) the next you enter will be numbered 4 eventhough no 3 exists anymore, as far as i can see this won't cause any problems since you are not really using this number to build you treeview. something like this is also done in databases by the way.
Thanks MarcG but that would mean i am stuck with the A-Z nodes and not able to expand the program at a later date if i need to.

I shall post some points for your help though.
i'm using the imageindex to perform a seek command to the record - if the numbers are all out then i can see problems when i add 10 records and delete 5 and then click a sibling with the record number 10 and try to seek to it when only 5 records are in the file.
I'm sorry, but I don't think I'm entirely understanding the problem.

John.
with luck this will make it a bit clearer (as mud at least)

Instead of using the buttons to goto the previous, next, first or last number i want to be able to click the sibling and use the imageindex number to seek to the record and display it.

the problem is that, there is only the ablitity to hold upto 2000 records so the imageindex numbers will go from 1 to 2000 for a full file - as long as i do not delete anything because when i do it will then be 2001 which if i try to seek to the record won't exist.

add to that the best way to save the extra info (the node title, the sibling title and the imageindex of the sibling) into the file structure and be able to go through the file and rebuild the treeview and you an see why i am having trouble figuring it out.

i've increased the points in the hope of getting help on this one as i am stuck on it.
could provide a working sample this evening,
but guessing thats too late :-(
Kretzschmar, if you can i shall award you the points if i don't have an answer by then.

if i do get an answer i shall post the same points for you (in fact i shall increase the points) in another post.
so you want to have some data with your record that makes it possible to identify it. again like in databases, add a key or id to it and if you click on a node you search your records for the right ID (that you set when creating the node)
by the way, I would suggest using the tag property for your numbering since it is not used for anything else and if you think about later needs it would be the better solution, what if you want to add pictures for example for male or female contacts ? then you would need the image index for it...
>Instead of using the buttons to goto the previous, next,
>first or last number i want to be able to click
>the sibling and use the imageindex number to seek to the
>record and display it.

use the onchange-event of your tree for this

>the problem is that, there is only the ablitity to hold upto 2000 records so the imageindex numbers
>will go from 1 to 2000 for a full file - as long as i do not delete anything because when i do it will
>then be 2001 which if i try to seek to the record won't exist.

you could only mark one as deleted in your file,
and at appstartup you could clean up the file, to delete the marked records from the last session

>add
just append to the file and add anode

meikl ;-)


excellent idea MarcG - duly noted, thanks!
>you could only mark one as deleted in your file,
should read
you could mark the record as deleted in your file
marcG,
this would cause an already sequential read of the file in every nodechange, to find the record
meikl, thing is that cleaning up would be the problem since all the records would not be stored in sequence 1...2000.  the file might be 1 ash, 2 williams, 3 jones, etc.
meikl, the onchange idea marcG gave is what i already use.  when i click the "ash" sibling it gives me the number in the imageindex which i then seek to in the file.  same for the other siblings.
>meikl, thing is that cleaning up would be the problem
>since all the records would not be stored in sequence

that doesn't matter, because i meant an attribute in your record in your file, which could be used for marking or a new attribute

following flow i have in mind

appstart
delete marked records
load the tree->remember current positions somewhere in the node (tag,data)
add(appendtofile),modify(replcerecord),delete(markrecord)
end of flow

meikl ;-)
sounds like a plan meikl - seeing it in working code would be the clinch.
just a thought about reading and rereading the file ... can't you make an array of record to store the records in memory (depending on how big it is but I think some MB of memory needed is not too much) then you can access them faster, and at the end write them back to disk.
as stated above, i can't do it before this evening,
because here at work i've no delphi installed
(working with oracle forms since months for a project :-( )

and i will not block other experts to provide solutions, which are helping you, sothat i will provide
a working sample if this q is unsolved. this may take
~8 hours from now.

meikl ;-)
MarcG, i looked at the idea of using an array instead of the current record method but it would put some restrictions on the program such as the amount of memory it used when it is run and would limit memory for other programs running.


Meikl, seems you have the answer.....help!! :)
yes, i guess, but got not the time yesterday to code it(family-action), sorry,
even ex-ex wasn't available to me since yesterday afternoon until this morning, didn't know why

well everyone can pick-up the idea and code a sample instead of myself, because i can't do it before late afternoon today (family is informed :-)
Well Meikl, looks that no one is able to help on this one.  i shall keep the question open and hope that you can get some spare time soon to help me on this one.  If you can help me solve my problem i shall increase the points to 400 plus an A grade.
well, you do not need inceasing points for me,
as my family is informed i get two hours this afternoon,
to implement this idea, guessing i need only one hour,
because i have some codefragment ready its just to complete so that it fits your needs

i provide it today, even if some other expert solved this q

meikl ;-)
thanks meikl, i look forward to your help and as for the extra points i offer - i think your help is worth it and it is my way of saying thanks.
Hi! If you can't use the database to store data,
you can work with compound storages.
Hi alx512, not sure i understand what you mean about "compound storage".  
well, here it comes, maybe not bugfree in all cases, maybe something could be done more elegant

unit r_file_tree_u;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls, ComCtrls, ExtCtrls;

Type TheRec = Record  //a record
                LastName  : String[100];
                FirstName : String[100];
                //more attributes could stand here
                Deleted  : Boolean;
              end;

type
  TForm1 = class(TForm)
    TreeView1: TTreeView;
    Panel1: TPanel;
    Edit1: TEdit;
    Edit2: TEdit;
    BInsert: TButton;
    BUpdate: TButton;
    BDelete: TButton;
    procedure FormCreate(Sender: TObject);
    procedure BInsertClick(Sender: TObject);
    procedure BUpdateClick(Sender: TObject);
    procedure BDeleteClick(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
    procedure TreeView1Change(Sender: TObject; Node: TTreeNode);
    procedure FormDestroy(Sender: TObject);
  private
    NodeList : TList;      //AList of the rootNodes
    f : file of TheRec;    // file of this record
    rec : TheRec;   {Buffer} // a memorybuffer for this record
    Currentrecord : LongInt; {FileCursor}  //Points to the current record in the file
    RecordCount : LongInt; {RecordCount} //Holds the amount of records in the file
    Procedure PopulateEdits;
    Procedure PopulateRecord;
    Procedure CleanUpFile_at_Back;  //looks at the last record, if this is deleted, and deletes it
    Procedure CleanUpFile;          //Deletes all records, which are marked for deletion
    Procedure InitTree;             //adds the level one nodes
    Procedure LoadTree;             //adds the level two nodes from files
    Procedure GetRecord(ARecNo : LongInt);  //gets a record
    Procedure UpdateRecord;         //Upsate a Record
    Procedure MarkToDeleteRecord;   //Marks a record for delete
    Procedure InsertNode(AName : String; ARecNo : LongInt; DoSelect : Boolean);  //Inserts a Node into the tree
    Procedure AdJustControls(Switch : Boolean);  //Dis-/Enables Buttons
    Procedure InsertRecord;         //Insert a Record
  public
    { Public-Deklarationen }
  end;

var
  Form1: TForm1;


implementation

{$R *.DFM}

//open the file
procedure TForm1.FormCreate(Sender: TObject);
begin
  assignfile(f,'MyFile.Dat');  //we will work with this file
  if not(fileexists('MyFile.Dat')) then  //if this file not exists
    rewrite(f)  //create this file
  else
    reset(f);   //open this file
  CurrentRecord := 0;      //
  RecordCount := fileSize(f);
  NodeList := Tlist.Create;
  LoadTree;
end;

Procedure TForm1.InsertRecord;
begin
  PopulateRecord;
  rec.Deleted := False;
  seek(f,RecordCount);  //goto last record
  write(f,rec);
  inc(RecordCount);
  CurrentRecord := RecordCount;
  InsertNode(rec.LastName,CurrentRecord,True);
end;

procedure TForm1.BInsertClick(Sender: TObject);
begin
  InsertRecord;
end;

procedure TForm1.BUpdateClick(Sender: TObject);
begin
  UpdateRecord;
end;

procedure TForm1.UpdateRecord;
var
  ParentSwap : Boolean;
begin
  if CurrentRecord > 0 then
  begin
    PopulateRecord;
    //must we Move the ChildeNode
    ParentSwap := ((TreeView1.Selected.Text <> '') and (rec.LastName <> '') and
                  (UpCase(TreeView1.Selected.Text[1]) <> UpCase(rec.LastName[1]))) or
                  ((TreeView1.Selected.Text <> '') and (rec.LastName = '')) or
                  ((TreeView1.Selected.Text = '') and (rec.LastName <> ''));

    TreeView1.Selected.Text := rec.LastName; //Update NodeText
    seek(f,CurrentRecord-1);
    write(f,rec);
    If ParentSwap then
    begin
      TreeView1.Selected.Delete;
      InsertNode(rec.LastName,CurrentRecord,True);
    end
    else
      TreeView1.Selected.Text := rec.LastName; //Update NodeText
  end;
end;

Procedure TForm1.MarkToDeleteRecord;
Begin
  rec.deleted := true;
  seek(f,CurrentRecord-1);
  write(f,rec);
  treeview1.selected.Delete;

End;


procedure TForm1.BDeleteClick(Sender: TObject);
begin
  MarkToDeleteRecord;
end;


procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  closefile(f);
end;

Procedure TForm1.AdJustControls(Switch : Boolean);
var I : integer;
begin
  BUpdate.Enabled := Switch;
  BDelete.Enabled := Switch;
  If Not(Switch) then
  begin
    edit1.text := '';
    edit2.text := '';
  end;
end;

procedure TForm1.TreeView1Change(Sender: TObject; Node: TTreeNode);
begin

  If Node.Level > 0 then
  begin
    GetRecord(Integer(node.Data));
    PopulateEdits;
  End;
  AdjustControls(Node.Level > 0);
end;

Procedure TForm1.GetRecord(ARecNo : LongInt);
begin
  If (ARecNo > 0) and (ARecNo <= RecordCount) then
  begin
    try
      seek(f,ARecNo-1);
      read(f,rec);
      CurrentRecord := ARecNo;
    except
      raise;
    end;
  End
  else
    raise exception.create('GetRecord : Recno Out of Bounds');
end;

procedure TForm1.PopulateEdits;
begin
  edit1.text := rec.FirstName;
  edit2.text := rec.LastName;
  //maybe more attributes
end;

procedure TForm1.PopulateRecord;
Begin
  rec.FirstName := edit1.text;
  rec.LastName := edit2.text;
  //maybe more attributes
End;



Procedure TForm1.CleanUpFile_at_Back;
var
  endloop : Boolean;
begin
  if RecordCount > 0 then
  begin
    endloop := False;
    while (not endloop) and (RecordCount > 0) do
    begin
      getrecord(RecordCount);
      if rec.Deleted then
      begin
        seek(f,RecordCount-1);
        truncate(f);
        dec(RecordCount);
      end
      else endloop := true;
    end;
  end;
end;

Procedure TForm1.CleanUpFile;
var
  tmpCurrentRecord : Integer;
Begin
  If RecordCount > 0 then
  begin
    tmpCurrentRecord := 1;
    CleanUpFile_at_Back;  //be sure the last record isn't deleted
    while tmpCurrentRecord < RecordCount do
    begin
      GetRecord(tmpCurrentRecord);
      if rec.Deleted then
      begin
        GetRecord(RecordCount);  //swap records
        seek(f,tmpCurrentRecord-1);
        write(f,rec);
        seek(f,RecordCount-1);
        truncate(f);         //snip
        dec(RecordCount);
        CleanUpFile_at_Back; //be sure the new last record isn't deleted
      end;
      inc(tmpCurrentRecord);
    end;
  end;
  CloseFile(f);  //force physically write
  reset(f);
  CurrentRecord := 0;  //reinit
  If (filesize(f) = RecordCount) then
    RecordCount := FileSize(f)
  else
    raise exception.Create('CleanUp : Something goes wrong');
end;

procedure TForm1.InitTree;
var
  ANode : TTreeNode;
  I : Integer;
begin
  NodeList.Clear;
  TreeView1.Items.Clear;
  for I := Ord('A') to Ord('Z') do
    NodeList.Add(Pointer(TreeView1.Items.Add(NIL,Chr(I))));
  NodeList.Add(Pointer(TreeView1.Items.Add(NIL,'Others')));
End;

procedure TForm1.InsertNode(AName : String;
                            ARecNo : LongInt;
                            DoSelect : Boolean);
var
  ANode : TTreeNode;
  AIndex : Integer;
Begin
  If (AName <> '') and
     (UpCase(AName[1]) in ['A'..'Z']) then
    AIndex := Ord(UpCase(AName[1]))-Ord('A')
  else
    AIndex := NodeList.Count-1;
  If (AIndex > -1) and (AIndex < NodeList.Count) then   //be sure not to be out of bounds
  Begin
    ANode := TreeView1.Items.AddChild(Nodelist[AIndex],AName); //Create Node
    ANode.Data := Pointer(ARecNo);  //Store recordposition
    If DoSelect Then
    begin
      TreeView1.Selected := ANode;  //Select Node
      ANode.MakeVisible;
    end;
  end
  else
    raise exception.Create('InsertNode: Something is wrong');
end;

procedure TForm1.LoadTree;
Begin
  CleanUpFile;   //Delete all marked physically
  InitTree;
  CurrentRecord := 0;    //load From Beginning
  seek(f,CurrentRecord);
  While not eof(f) do
  begin
    Read(f,rec);
    inc(CurrentRecord);
    InsertNode(rec.LastName,CurrentRecord,False);
  end;
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  NodeList.Free; //free the list of RootNodes
end;

end.

btw. i also do not know what compound storage is

ask if something unclear or not work as needed(only partially tested)

meikl ;-)
Meikl you're a genius!

The only question i have is if i enter some information (a record) under A for example, how can i identify the record number to display if i click on the record name in the treeview?

oops, ignore that last question meikl - i forgot to link in the treeview.onchange procedure you wrote.

one final question though, if i wanted to use different main nodes (instead of A-Z) would it be difficult to change all the code to allow for that?  example, if i wanted the main nodes to be "family", "friends", "co-workers" and a few others like that.


points have been increased for you to 400 meikl
strange, it would ex-ex would not allow me to increase it to 400 points.  said that the limit was 300.  so once this question is closed i shall post a further 100 points for you to claim.
ASKER CERTIFIED SOLUTION
Avatar of kretzschmar
kretzschmar
Flag of Germany image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Brilliant!!!

The 300 points are yours from this question and i shall also post a further 150 for you as well since you gave so much help and were patient with my problem.

I shall grade both with an A as well.
shall post the other 150 for you now