HOME > > > TUTORIALS TABLE OF CONTENTS / - - / - - / - - / - - - Other material for programmers

Delphi tutorial: Reading, Writing Files.

This has good information, and a search button at the bottom of the page

Please don't dismiss it because it isn't full of graphics, scripts, cookies, etc!

Click here if you want to know more about the source and format of these pages.

IGNORE ANY PERIODS (.) AT THE START OF ANY LINE

Simple File Handling.

This was first written long ago, originally in Delphi 1, for which it remains valid. In September 2002, I did the tutorial myself, using Delphi 2, making a few "tidies" here and there. If you find problems, do please let me know!

First: What do I mean by file handling? (For those of you who already know... be patient.. I'm only going to spend a about four paragraphs on this!)

A 'file' is something saved on a disc. Writing to a file is a bit like sending something to the printer. You have something in a variable, and you want it written down somewhere. The 'something' can be of any type- string, byte, integer, record. Continuing my analogy of printing as a way to understand writing to a file: Not only can you write to the file, but you can also put your something anywhere in the file that you want it, as if you were able to specify where on the page printing should occur. Often a file will be organised. The analogous piece of paper would have rows and columns marked on it, so you could say: "Write (it) in the second column of the third row."

The 'file handling as printing' analogy becomes a little strained when you learn that not only can you 'write' things on the 'paper', but if you overwrite something written earlier, a perfect 'erasure' occurs first.

The 'file handling as printing' analogy becomes even more strained when you turn to consider reading from a file. Not only can you write to a file, as discussed above, but you can also 'see' what has previously been put in any part of the file. Once everything else is in place, you have something like

function GetNthStringFromFile(bN:byte):string;
begin
GotoRecord(bN);
GetNthStringFromFile:=ReadStringFromFile;
end;

(Only function, begin, :=, string, byte and end in the above are built in elements of Delphi, by the way.)

Not only can you write to and read from files, but you can also delete them, rename them, move them, copy them etc.

So... that's what filehandling is. Now on to doing it...



A warning: Delphi (version 1, anyway) seems to incorporate at least three systems of general file handling, and several options for doing specific file related jobs. Furthermore, traditional file handling keywords are sometimes used for other jobs. Built in to Delphi, for instance, we have: Close, CloseFile and FileClose! I am not going to do a comprehensive review of Delphi's file handling. I am just going to give you some tools that can be used on almost any file handling job. At the end, I will mention some of the built in options for specific jobs.

Just to give this a focus, I will work up a 'High Scores' table, such as you will find in many games. I won't go into the details of what game this high scores table serves. We will require that the high scores table be held in the file 'HighS.txt', in the same directory as the game's .EXE file.

((( An aside: If you put HighS.txt and the game's .EXE in the same directory, the program will find it without hassle UNLESS (Win95, at least), you've put them both on the desktop. However... you can put them both in any OTHER directory, and put a simple shortcut to the .EXE file on the desktop, and all will be well.)))

The high score table will keep the top ten scores and the name of the player. The score will always consist of three digits. The player name will always consist of 25 characters, though some right hand characters may be spaces. Each top player's score + name is called a record. During most of the program's execution, the ten records in HighS.txt will also be available in the array sSN (strings of Score-plus-Name). Each element of SN is a 28 character string. The first element (top score) is element 0.

In outline, what we need for the game program is..

When game starts:
... if HighS.txt does not exist, create one, and leave it 'open'.
.....................(More on what 'open' means in a minute)
........else open HighS.txt
Copy what is in HighS.txt into the array sSN[0..9]
Repeat
....Let player have a go
....See if his score is higher than that in sSN[9]
....If it is, revise the array sSN and
.........write it to the disc, replacing the old HighS.txt
Until no one wants to play any more
Close the file (More on 'close' in a minute')
End the program

((Another aside: The 'write it to the disc' step could have been put after the 'until' if you wanted to save disc thrash... but that would leave the program vulnerable to the sort of people who fail to exit programs by the intended route.))

First bit of good news: There is a built in function FileExists. You can just say if "FileExists('HighS.txt') then..." and the program will do what you would expect.

((Aside: You may know enough to see that the following uses an elephant gun to shoot a mouse. My intention is to show all of the elements needed for general file handling, not to devise the best possible High Scores handler.))

Now then... 'Open' and 'Close'. The program can't write to a file unless you say WHICH file. You don't want to say which file every time you do anything with it, so the idea of a file type variables arose. Before you do any reading or writing, you 'open' the file. After that the file variable is your way of referencing the file when you want to work with it. (You can have several files open at the same time, by the way.) NB: In the system of file handling in Delphi that I use, the word 'open' isn't used to open the file. You either 'reset' it, or 'rewrite' (misleading) it. (Explanation of their differences: to follow.)

Get your Delphi fired up. Start a project DD02 (Delphi Demo), with a form DD02f1, unit DD02u1.

Get yourself into DD02U1.PAS's window.

Make the second line {$R+}. (If you want to know more about this detail... unrelated to file handling... look in the help file under compiler directives.) NB::: DON'T turn the line {$R *.DFM} into {$R+ *.DFM}!! {$R followed by a file spec has a completely different function, and putting the + in is disasterous!

Just after 'var' and 'DD02f1: TDD02f1;', add...
df:file of byte; (*df for 'Data File'*)

Click on the events tab of DD02f1 in the Object Inspector. Double click on the empty field to the right of 'on create'. You find your cursor is now in DD02U1.PAS's panel, between 'begin' and 'end' of the FormCreate procedure.

Enter....
assignfile(df,'HighS.txt');
rewrite(df);
closefile(df);

((Aside: I can NEVER remember if it is 'rewrite' or 'reset' I want for this... so I just type whichever comes to mind and do ctrl-f1, and up comes the help file to tell me what I need to know! Don't forget ctrl-f1... use it often.))

Save your project.... do it often... reading and writing to files can do all sorts of wonderful things (crash system). THEN try to run what you have so far. You'll only get a blank window, but if you do, all is well so far! End your program. (Alt-f4, or the easier to use/ harder to describe Win95 or Win 3.x alternatives)

(Aside: either put a 'Quit' option in a menu, of if you're not happy with menus yet, make a 'quit' button... all the 'onclick' handler needs to be is application.terminate)(I HOPE that's ALL you need. Can any expert confirm that Delphi cleans up after sloppy programmers who terminate without first closing any files which happen to be open?)

After df:bytefile; add
sSN:array [0..9] of string[28];(*Score plus Name*)

After procedure TDD02f1.FormCreate(Sender:TOBject); add
var c1:byte;

Save project, run. Still... 'nothing' happens... just checking material added so far.

After rewrite(df); add

for c1:=0 to 9 do sSN[c1]:='00'+IntToStr(9-c1)+'-NoNameABCDEFGHIJKLMNOPQR';

Save, run.

We're now going to do something that isn't strictly necessary for file handling, but will help us debug this project. We are going to do it the hard way, because I still don't know how to do it properly, i.e with watch/evaluate. We're going to put a memo on our form so that we can see what is in each element of sSN.

Put a memo on the form. (The memo component is on the 'Standard' tab of the component palette.

Click on the TStrings beside the object's Lines property.
Replace 'Memo1' with 0. Press enter. Type 1, press enter, etc, until you have the digits 0 to 9 in a column in the String List Editor window. Click OK. (No need to save).

Adjust size of memo on form to be more than big enough for the 0 to 9 vertically, and 000-NoNameABCDEFGHIJKLMNOPQR horizontally.

After sSN[c1]:='00'+IntToStr(9-c1)+'-NoNameABCDEFGHIJKLMNOPQR' add

for c1:=0 to 9 do Memo1.lines[c1]:=sSN[c1];

Save project, run. At last it does something you can see! Add the following just before the "closefile(df)..."

for c1:=0 to 9 do WriteLineToFile(sSN[c1]);

Before you can run that, you need to provide a WriteLineToFile procedure.

Just after the var c1:byte; near the start of procedure TDD02f1.FormCreate... add:

procedure WriteLineToFile(sTmp:string);
var c1,c2:byte;(*Don't try to do away with any of the three uses
........of c2... the complier won't like it!*)
begin
for c1:=1 to length(sTmp) do
begin
c2:=ord(sTmp[c1]);(*sTmp[2] returns the second character of the string sTmp*)
write(df,c2);
end;
c2:=13;write(df,c2);(*This plus next will allow editing datafile*)
c2:=10;write(df,c2);(*with Notepad, which is sometimes useful*)
end;

(*Notepad files do not always have the 13/10 pair after the last line, by the way.*)

If you load HighS.txt into Notepad, you should see a bunch of lines consisting of a number and a hyphen followed by 'NoNameABCDEFGHIJKLMNOPQR'.

So far so good! Go make a cup of coffee- you deserve a break.

We have to go back to 'rewrite' and 'reset'.

Assign(df,'HighS.txt');rewrite(df); WILL open a file called HighS.txt. Unfortunately, if there is already a file of that name, the old file will be destroyed in the process... not very helpful if you want the people who had high scores last time to still show in the table the next time the program is run!

First, we'll fix things so that if there isn't a HighS.txt already, the program will make one. Just after the existing 'Assign(df...' line, add

if not FileExists('HighS.txt') then begin
.... and just before the existing 'closefile(df);' put
end (*no ; here*)

And immediately following the "...end (*no ; here*)", above, you put in the following, which reads the old HighS.txt if there was one:
else begin
reset(df);
for c1:=0 to 9 do sSN[c1]:=ReadLineFromFile;
for c1:=0 to 9 do Memo1.lines[c1]:=sSN[c1];
end; (*else*)

Again, you'll have to add the ReadLineFromFile function before the program will run. It can go just before 'procedure WriteLineToFile..'

function ReadLineFromFile:string;
(*Not a very generalised read: Assumes 28 byte strings followed
by two 'waste' bytes*)
var c1,c2:byte;
sTmp:string;
begin
sTmp:='';
for c1:=1 to 28 do begin
read(df,c2);
sTmp:=sTmp+chr(c2);
end;
read(df,c2);read(df,c2);
ReadLineFromFile:=sTmp;
end;

Once that appears to be working, you can do the following test:
Run the program once, then quit it.
Load HighS.txt into Notepad.
Change the first character to 'X'. Be sure you CHANGE the first character, don't just ADD an X... the line must have the same length after the change as it had before it.
SAVE the new HighS.txt (from Notepad).
Run your program again... you should see 'X09-NoName...' as the first line.
Delete HighS.txt
Run your program again.
You should once again have a 'clean' HighS.txt.


That pretty well covers the essentials. For the game, of course, you would have to get the array sSN updated any time someone achieved a new top ten score, and then execute

assignfile(df,'HighS.txt')
reset(df);
for c1:=0 to 9 do WriteLineToFile(sSN[c1]);
closefile(df);

It is important to match your reset/rewrite statements with closefile statements. Delphi is pretty forgiving about programs closing while files are still open, but the situation is best avoided. It is NOT a good idea to do a new assign(df,'') if you have not done closefile(df). You can re-reset a file. If you were to rewrite a file which had had things done to it already, those things (and anything else already in the file) would be lost.

It is also important not to have any extra closefile(df)'s. One solution is to declare a variable called boFileOpen. Set this false in OnFormCreate. Set it true when the file is opened. Then you can have closefile(df)s whereever you might need them, but put them in...
if boFileOpen then begin
  closefile(df);
  boFileOpen:=false;
  end;
... but you shouldn't need this in a simple program. You should know if the file is open!

Time for another break!

In the above, we assumed that the file would consist of exactly 10 strings, each 28 characters long, each (including the last) followed by two bytes we weren't using. Life is seldom so simple!

Happily, there is a function called eof (End Of File).

If I get sufficient encouragement from feedback from THIS tutorial, I may write up a more obscure, but more generalised, routine for reading from a file into the elements of an array. Something like the following would do to read a very short string of characters into a string type variable:

assignfile(df,'Short.txt');
reset(df);
sTmp:='';
repeat
read(df,c1);
sTmp:=sTmp+chr(c1);
until eof(df);
closefile(df);

Implicit in the preceeding is the fact that eof becomes true WHEN you read the last item in the file. You don't have to TRY to read PAST the end of the file to make eof return true.


Other useful words you should know about are seek(df,n), FilePos(df), FilesSize(df). There are entries for each in the help files. In the Delphi 1 help file, look under 'I/O Routines'. (The section on 'File-management routines' takes you into a whole other system of reading and writng files. Some of the things there (e.g. FileAge) are useful, and won't clash with the things already explained. Others will, e.g. FileClose. Stay away from things involving an integer type parameter called a handle.)

Seek is particularly useful. It allows you to move to a particular point in the file for your next read or write. Rewrite and Reset both perform a seek(df,0), i.e. if you read or write after either, you will read or write at the start of the file. (As rewrite erases any existing file, any write would have to be at the file start, and no read would be possible.) Read and write both move the pointer forward, as you can tell from the fact that our program worked.

The following would tack an 'A' onto the end of a file. From it, I hope you can see how to use seek to append something to an exisiting file.

if fileexists('Short.txt') then begin
assignfile(df,'Short.txt');
reset(df);
seek(df,FileSize(df));
c2:=65;(*Ascii for 'A'*)
write(df,c2);
closefile(df);
end;(*fileexisits*)


Things to avoid: You may have to shut down your machine and re-boot if you do the following:

assignfile(df,'HighS.xxx');read(df,c1);
(Attempt to read from a nonexistent file AND without a reset or rewrite before the read.) Having {$R+} at the start of the unit won't save you from the crash. Even adding reset(df); won't help if the file does not exist. You should have an explicit FileExists('HighS.xxx') test before any reset call.


Pascal type strings(the ones I've been using, as opposed to pChar type strings (topic for a whole other tutorial!) can only be up to 255 characters long. You may get away with a file access which results in something equivalent to...
sTmp:='';
for c1:=0 to 500 do sTmp:=sTmp+'X';.......
... but I wouldn't recommend letting the situation be possible.
{$R+} won't catch it, by the way. Even if you declare a type string80=string[80]; and declare sTmp to be of that type, even with {$R+} in effect, the program won't complain. (It won't crash every time, though! Still best avoided!)

{$R+} won't catch: var c1,c2:byte;c2:=250;for c1:=0 to 100 do inc(c2);
This is because range checking isn't applied to the inc (and dec) functions. Replace inc(c2) with c2:=c2+1 to obtain range checking. I'd guess there's an execution speed penalty, but it will probably be worth it, except maybe in an inner loop that executes many many times.
{$R+} will, however, catch
var s:array[0..5] of byte;
c1:byte;
begin
for c1:=0 to 20 do s[c1]:=123;


Now I'll show you something more, using a fanciful example. Suppose you wanted to take file 'In1.txt' containing 'ABCDEF' and file 'In2.txt' containing '123456', and from them create file'Out.txt' consisting of 'AB12CD34EF56'. Surely, we can have one 'get two characters from file' function, and use it to look at either of two (or more) input files? (Yes, we can.) Notice also that this program doesn't assume that you will have the whole of any of the files involved in the computer at any time.

I'm not going to build the whole thing up bit by bit. The following is the heart of it all. Tell me if you find any errors or difficulty in working it up from the indications here.
procedure TDD03f1.FormCreate(Sender: TObject);
type ByteFile=file of byte;
var df1,df2,df3:ByteFile;
c1,c2:byte;
sTmp:string;
function ReadTwo(var df:bytefile):string;(*'file of byte' unacceptable. You have to define a type, and use that*)
var sTmp:string;
    c1:byte;
begin
sTmp:='';
read(df,c1);
sTmp:=sTmp+chr(c1);
read(df,c1);
sTmp:=sTmp+chr(c1);
ReadTwo:=sTmp;
end;
procedure WriteTwo(sTmp:string);
var c1:byte;
begin
c1:=ord(sTmp[1]);write(df3,c1);
c1:=ord(sTmp[2]);write(df3,c1);
end;
begin
assignfile(df1,'In1.txt');
assignfile(df2,'In2.txt');
assignfile(df3,'Out.txt');
reset(df1);
reset(df2);
rewrite(df3);
for c1:=0 to 2 do begin
sTmp:=ReadTwo(df1);
WriteTwo(sTmp);
sTmp:=ReadTwo(df2);
WriteTwo(sTmp);
end;
closefile(df1);
closefile(df2);
closefile(df3);
end;


The programs we've looked at so far use "file of byte", and read or write single bytes one at a time. If the file consists only of lines of text (like our High Scores project), you declare the file variable as type textfile, and then you can pick up a whole line with readln(df,sTmp);

If the file consists entirely of lines of text, and you are content to have the whole file in memory at once, you can use LoadFromFile, which is built in to Delphi.
Create a form with a memo called Memo1 (default name). Then do:

Memo1.lines.loadfromfile('SomeFile.txt');

If you have a file called SomeFile.txt, it will have been loaded into the memo.

If you want to work with .ini files, there is a whole bunch of tools for that. See the IniFiles Unit help for a starting point into this topic. (You CAN do .ini files the way we did the High Scores, too, though.)

File handling is an important part of programming. I hope this tutorial has helped get you started. And my thanks to Eugene V from St.Petersburg in Russia (I had an interesting visit to that lovely city in December 1984) for pointing out a little flaw in this as originally published. It could have caused beginners some real headaches.


?????????????A mystery for the experts: I'd welcome an explanation, if anyone can provide one!

At one time, I was suggesting that near the start of this program you should have, just after the "end;" after the comment "{public declarations}",
ByteFile=File of byte;
String28=string[28];

var
DD02f1: TDD02f1;
df:ByteFile;sSN:array [0..9] of string28;(*Score plus Name*)
That has now been replaced by the more transparent
{ByteFile=File of byte;  This now gone}
{String28=string[28];    This now gone}

var

DD02f1: TDD02f1;
df:file of byte;
sSN:array [0..9] of string[28];(*Score plus Name*)
Whatever possessed me to do things the more complicated way? What permitted me to establish ByteFile and String28 as aliases for what I wanted? Both structures seem to work in Delphi1 and Delphi2. Ah well.. It's fixed now, and simpler is always better... when it works!
A slight digression about converting between Delphi 1 and Delphi 2. I frst wrote this tutorial a long time ago, using Delphi 1. Because of a helpful reader pointing out a problem, I went through the tutorial myself, as a pupil. At the same time, I decided to check it for Delphi 2 compliance. All was well, although there are things (not present in this program) which need attention if you migrate a Delphi 1 program to Delphi 2, or vice versa.
After the Delphi 2 version was done, I decided to see if it would run in Delphi 1. I copied the DD02u1.pas, DD02u1.dfm and DD02.dpr files to a machine with Delphi 1. In the Uses clause, Delphi 2 puts a unit named "Windows". For Delphi 1, replace that with the two units named WinTypes and Win Procs.
I then tried to run the program and began getting messages about "line too long". You may or may not see htis. My machines were behaving strangely at this point. I think what was happening is that the non-printing characters marking the ends of lines had become confused. As few of us are going to do much Delphi 2 to Delphi 1 conversion, I'm not spending a lot of time on this. I did, eventually, get the code of the delphi 2 version to run happily in Delphi 1, with only the change mentioned already to the units listed in the uses clause.
   Search this site or the web        powered by FreeFind
 
  Site search Web search
Site Map    What's New    Search


Click here if you're feeling kind! (Promotes my site via "Top100Borland")
Ad from page's editor: Yes.. I do enjoy compiling these things for you... hope they are helpful. However.. this doesn't pay my bills!!! If you find this stuff useful, (and you run an MS-DOS or Windows pc) please visit my freeware and shareware page, download something, and circulate it for me? Links on your page to this page would also be appreciated!

Click here to visit editor's freeware, shareware page.


Link to Tutorials main page
Here is how you can contact this page's author, Tom Boyd.


Valid HTML 4.01 Transitional Page WILL BE tested for compliance with INDUSTRY (not MS-only) standards, using the free, publicly accessible validator at validator.w3.org


If this page causes a script to run, why? Because of things like Google panels, and the code for the search button. Why do I mention scripts? Be sure you know all you need to about spyware.

....... P a g e . . . E n d s .....