HOME - - - - - Delphi Tutorials TOC - - - - - - - - - - - - Other material for programmers

Delphi: A "real world" example of file handling

Data file handling, with OpenDialog / SaveDialog, and user written modal dialogs

With "pop up" user written dialogs, passing values between units, and the Delphi OpenDialog and SaveDialog components thrown in along the way!



The layout and formatting of this tutorial is not as polished as most.. yet!... but there is good information here.

You can download a zip file with the source code, and a sample of the real data this application was written to massage.



Introduction

This tutorial is an extension of one with the basics of reading and writing data to files. You might want to read that first, but you can start here if you want to. This extends the boLineIsWanted function, making it capable of reading a date in the data that I wanted to massage. Also extensively extended are both the procedure for specifying the source file and the procedure for specifying the destination file. Along the way you are shown how to use the valuable and easy- to- use (once you know how!) OpenDialog and SaveDialog. And almost "accidentally", along the way I learned... and pass on to you... how to create a modal dialog of your own. ("Modal dialog"? One of those "windows" that pop up to get some information from you, and then go away when the information is supplied.)

Written using Delphi 4, but the points here should be valid in any version apart from 1. (Most points valid even there!)

This "tutorial" will be less "helpful" than is typical for the tutorials on the site it comes from.

Rather than a "how you do it", it is a "here's one I did earlier".

The sourcecode is much more heavily commented than usual. Those comments are "the tutorial" this time.

Some of the techniques demonstrated are specific to the task the application was built for. In addition, you will see other "tricks of the trade".

There are many ways to read and write data to files... and I have written several tutorials on the subject. The method shown here works with text files. It is more labor intensive than the "put text in a memo" approach, but it also handles huge files... potentially as big as your hard drive... with less strain on your memory. It also introduces you to concepts that you can use to handle non-text data.

Over the years I've written several tutorials on file handling, among them another tutorial that covers some of the same ground covered by this tutorial. You may want to give it a try later to help consolidate things you learned here. Some of the others cover quite different approaches to data file handling.


The objectives of the application are as follows. You can skim the next two paragraphs, because they are just to explain the precise details of why I wrote the application. Then I turn to the general aspects of the application so that you can see to what sorts of jobs you might apply the techniques in it.

My weather logging system creates a text file with a line of text for each weather observation. If the system is rebooted for any reason, the data log is used to rebuild the graph of the past week's weather. (By the way, my FarWatch system allows you to see my weather logging software in action.)

From time to time, I "harvest" old data, copying it onto an external hard drive. I then massage the data file on the system recording the weather data, deleting the lines relating to events more than a week old.

So, in general, what this application does is...

The application will do nothing to the source file... the application will only be read from it.

Because it decides many issues, and because for my needs, I can, at this early stage I am deciding that the application will work as follows:

Until a certain line of the source file, nothing will be copied. From that line onward everything will be copied. The definition of "certain line" will be dealt with in a moment.

If you had different needs in mind, don't despair. As long as the order of the lines in the two files is the same, it won't be hard to adapt the application in this tutorial to make it satisfy other requirements. As an artificial example, it would be easy, for instance, to generate a destination file holding every fifth line from the source file.

Another thing that may or may not affect how much work you have to do is the fact that I am going to be working with data from a text file. It is actually produced by another application, but could in theory be created with Notepad, or the superior Textpad.

A sample of the real data I wanted to massage is included in the zip file with the source code for the application this tutorial creates.

If you've read the other tutorial, the next few paragraphs will be familiar, but skim through them again, anyway.

The program uses the enabled property of several buttons to ensure that users do only what is sensible.

The form has the following buttons....

I had the form, the interface between my application and its users, in mind before I ever began designing the code, or even the outline which led to the code. As you gain programming experience, you will be able to design forms FIRST, which is the way to go. Think about your USERS (customers). What do they want? Make the application give them that. The code which makes the things on the form do what they do can come SECOND. (Of course, it takes a little while to reach the point where you don't come up with forms which have buttons like "Give me $100000" or "Fly me to the moon." As your knowledge of what the computer can do (on the surface, and behind the scenes), your ability to design do-able forms will grow. But, even from the start, TRY to work from interface to code.

(The files will be closed after the copy is done, as part of the "Copy lines..." button's OnClick handler, but there's not button for that as the human users shouldn't be troubled with that computing detail. Any more than they were troubled with the opening of the files when the files were specified.)

When the application starts, only the "Specify source file" button is enabled. "Copy lines to..." doesn't get enabled until all of the other steps have been completed.

The programmer must remember that users might backtrack. I.e. the "Specify source file" button might be clicked again after a user had already done that, and, say, the next two steps once already. Providing for all of the paths a user might take through an application can be a real pain. The boDFInIsOpen and boDFOutIsOpen variables are examples of that sort of pain.

==============

In the previous version of this program, DE41, we had most things in place.. but some of them were done very crudely.

In particular, the input file was hardcode to be, and only be, C:\TmpTest.txt, and the output file was C:\TmpTestOut.txt. Also, if TmpTestOut.txt existed when you ran the program, it was simply overwritten... without so much as even a notification!

The application that goes with this tutorial fixes those limitations. Two components had to be added to the form, both from the "Dialogs" tab of the component palette. Both were "invisible" components, like timers and menus. They give rise to small squares on the form when you are looking at it in design view, but your user doesn't see anything. The two components were an OpenDialog (for opening files) and a SaveDialog (for saving them). (I have another tut about such things at q-ref, but bear with me here for a moment... this exposition is probably better!)

In fact, both of them are more about specifying WHICH file you want to open or close. When you drag the components onto your application's form, you add objects to the application. The default names (OpenDialog1 and SaveDialog1) are fine. You use them in similar manners, I'll explain using the OpenDialog as my example.....

For experimentation, add an extra, temporary button to the form. Make it's OnClick handler....

procedure TDE42f1.Button1Click(Sender: TObject);
begin

      then showmessage(OpenDialog1.filename);
end;

Run the application, click the button, navigate to a file, and click "Open".

That's "all there is to it"... if everything goes according to plan. Of course, humans operate applications, and so things rarely go to plan. But you've seen most of what you need in order to write code to open a file.

The details.

First, of course, the...

if OpenDialog1.execute

... had to be moved to buSpecSourceClick.

A number of it's properties were changed from their defaults in the section of TDE42f1.FormCreate which you will see if you look.

By setting OpenDialog1.InitialDir to 'C:\', then when the dialog starts up, you will be looking at the root of the C drive. I'd rather look at my desktop, or my My Documents directory ("Directory": old name for what we now call folders).... but that is not an easy thing to arrange in a way that will work across different people's systems. I haven't checked the information, but at pages.cs.wisc.edu/~rkennedy/... I found an article which looked like it might be helpful. The "solution" there, however, meant adding a .dll to your system which, even if it does come from Microsoft, is just one more hassle to deal with. It did say the solution would work with all versions of Windows.

There's also a good article at Wikipedia about the wretched "Special" folders.

For now, I'm going to struggle with navigating from C;. If you discover how to set InitialDir to MyDocs, I'd love to hear from you!

You may want to set the filter property, to give users a number of filtering options .You can use the Object Inspector, or do it with a line of code in your FormCreate procedure....

Filter:='Text files|*.txt|All files|*.*';

If set this way, string must consist of pairs of values separated by |'s (ASCII 124). The first member of the pair is what you'll see in the filter pulldown, and the second is the filter which will be applied. Note you can, if you have a reason, do things like setting a filter which shows, say, text files with names beginning with "X". (Filter would be X*.txt)... but be a little cautious about "corrupting" the way common Windows components behave... You don't want your users confused!

If you use the Object Inspector to set the Filter property, fill in the grid as follows....

Filter Name   Filter
Text files    *.txt
All files     *.*

... then you will get the same result as the line of code produced. If you try to set the Filter property (or any other) both ways, the work you did with the Object Inspector will be over-ridden by the line of code.

Now we come to the many bits (both in the everyday and computer senses) of the OpenDialog component's "Options" property. Again, you can set them with the Object Inspector. (Double click on the word "Options" to expand the item) They default to "false".

[ofReadOnly,ofFileMustExist,ofEnableSizing]

Would tweak the OpenDialog very nicely for the needs of this program. The ofFileMustExist is particularly important. It saves us a BUNCH of code to take care of "what if" things. The Delphi help file will tell you all about what these set, if someone tells you to look under "TOpenOptions" (not "TOpenOption"... "s" left off.)

You can have the best of both worlds. Use the Object Inspector to create the list of things you want true. If you look sharp, you will see, next to "Options", the string you need to incorporate in your code to create the line above. Long live Copy/ Paste!

(I'm not sure that what we are doing is going to respect the "Open for Read Only" we have specified... but there is no danger of our application (if it doesn't get corrupted) writing to the file we open as the source.)

(You may want to look at the short buSpecDestClick code before you read what comes next. I think the code is more obvious than my explanation!)

All that the Execute method of the OpenDialog component does is...

a) get a file name (one that is known to exist, if the ofFileMustExist option is set)

b) return "true" or "false"

Previously, we opened a specific file very crudely, and then "did things"... including "turning on" the button for the next step in the application's task.

With very little additional programming, we have greatly increased the flexibility of the program.

We started with....

if OpenDialog1.execute then begin...
    end;//if OpenDialog

... and put all of the "did things" stuff inside the then begin... end.

When does OpenDialog1.execute return "false"? When the user clicks cancel to get out of the dialog without specifying a file.

There may be times when users are allowed to decline to specify a file, but in this application, there's no point in proceeding if no file has been specified. Hence the other buttons remaining turned off.

========

The above is a good chunk of what you can "learn" in this tutorial, if you did the introductory one first.

Next we will use similar skills to incorporate the SaveDialog into the application.

Note that once again I tweaked SaveDialog1's options, in FormCreate.

Have a look at the sourcecode to see at least one tiny twist in the story.

===========

Only two things are left to create the application that I needed.

1) We will extend the code which selected which date is the cutoff for lines of data NOT copied into the destination file.

2) You probably don't need it, but for me, I need to implement the actual boLineIsWanted function called for by what my application is supposed to do. We will replace the kludge which acted as a placeholder until now. Again I will defend the use of the kludge. Properly constructed, they have a place in program development.

==========

Date specification:

At the end of buSpecCritClick, we need a number in wDate. It should be the day, in Delphi's code, of the last we DON'T want data from. Not the first day we DO want data from. Always take care not to confuse such things.... it is easily, and disastrously, done.

Delphi's code for days: 1 Jan 1996 was "day 35,065" January 2nd 1996 was 35,066, etc. Word type data can hold 0-65535, so there won't be a "Y2K" type problem for my application until about 30,000 days after January 1996.

=== DIVERSION begins...

While I said this tutorial wouldn't be didactic, I'll make an exception here. I'm going to show you, for the first time as of 12/08, at least for my collection of tutorials, how to make a modal dialog of your own. We use them all the time... the OpenDialog we've just been working with is one.

We're going to make the Specify Start Date button bring up a new window, which will be a dialog for entering the date.

To provide for this, first use Delphi's "New" menu item, click new again, then click on the icon for "form".

Name the form DE42DateInputf1, and save it and its code as DE42u2. Be sure the form's "visible" property is set to False in the Object Inspector.

DO NOT be tempted to put a DE42DateInputf1.hide in DE42f1's FormCreate! (It won't work, for good reasons, and may cause problems.)

Just to be tidy, in DE42DateInputf1' Form Create, put...

BorderStyle:=bsDialog;
BorderIcons:=[];
Caption:='Input Date';

Put a button on it. Make the button's OnClick handler...

procedure TDE42DateInputf1.Button1Click(Sender: TObject);
begin
  close;
end;

Go back to the code for DE42f1. Leave the existing code in buSpecCritClick alone, but, as the first line after the "begin", add...

DE42DateInputf1.showmodal;

The "modal" part of that means that after DE42DateInputf1 comes up, it will "seize control" and you won't be able to go back to the other form until DE42DateInputf1 closes. Just as well we put that button on it! (If you didn't, you can get out of your Catch-22 with alt-F4, or by using Delphi's Program Reset.

So. We've got a dialog (special instance of a form) popping up and going away... but shouldn't it do something useful?

Extend TDE42DateInputf1.Button1Click, making it....

wDateFromDE42DateInput:=trunc(now)-7;
close;

(The "trunc(now)-7" merely uses a built in Delphi function and some massaging of what it returns to put the Delphi number for the date a week ago into the variable. This was covered in more detail in the tutorial that led to this one. Don't be distracted by the detail!! (The trunc is not only to remove the fractional part, but also to cast the data to a different type.))

You will, of course, also need to declare the new variable. Do it in the public section at the top of TDE42DateInputf1's code....

public
    { Public declarations }
    wDateFromDE42DateInput:word;

If you haven't had a complaint from the system yet, add DE42u2 to the USES clause at the top of ** THE OTHER ** form's code. (The system may already have asked you about using the new unit. If it has, it put a new USES clause in, a way below the main USES clause.)

Now, back in the first bunch of code, the code for DE42f1, in the unit DE42u1, add another line to the buSpecCritClick procedure, so it starts....

begin
DE42DateInputf1.showmodal;
showmessage(inttostr(DE42DateInputf1.wDateFromDE42DateInput));

(The old code should still be there too.)

Now when you run the program....

1) As soon as you click the "Select Start Date" button, your new form, your dialog, comes up, and you are locked into it.

2) When you click on the dialog's button, a number is put in wDateFromDE42DateInput, and a moment later the dialog is closed.

3) This brings you back to the first form, which has been "in suspended animation" in the meantime. The showmessage occurs, and you see the number that was put in the variable when you were in the dialog. Had you used a mere....

DE42DateInputf1.show;

... not a showmodal, the showmessage would have been executed almost immediately. It would have shown what was in wDateFromDE42DateInput BEFORE it was filled due to our clicking the button on the dialog. (Which would be unfortunate, because wDateFromDE42DateInput would not yet have been initialized, and could be holding garbage.)

Not only have you seen how to produce a dialog, how to suspend the activity of the main form while the dialog is "showmodal'd", but you have also seen how to get numbers FROM the dialog, BACK TO the main form.

... End of didactic diversion ===

The final version of the Get Date dialog has various things in it which may be of interest, but I am returning to my original plan, which was to make this tutorial stand primarily on the sourcecode which comes with it, which you can inspect at your leisure.

The dialog will, as it has done ever since it had....

wDateFromDE42DateInput:=trunc(now)-7;

... as part of what the button did, will just return a number. Previously, the buSpecCritClick procedure was very basic. With the enhanced user input options, some checking of the value supplied by the user is probably warranted. Although the "output" from the GetDate dialog could, in theory, be vetted there, it makes more sense to leave that general, and capable of producing (almost) ANY date, hence the "repeat...until" loop within buSpecCritClick.

By the way: Did you spot the "deliberate error"? All though the discussion of the programming of the application, I've made a point of saying that "the date" is the last day we DON'T want data from. And then, oops, I designed an interface that talks about the date FROM WHICH I want data to be transcribed. Oh well. Manageable, but it will take care to keep things right.

An aside: We've seen how data from DE42DateInputf1 (our Get Date dialog) can be made accessible to the main form, DE42f1. It is also possible to send data the other way. There are ways to get in a terrible tangle doing this sort of thing, but the wind is behind my back tonight, and what I did worked!

1) In the USES clause at the top of DE42DateInputf1, I added DE42u1, the name of the main form's code.

2) I put a variable (wTest) in the PUBLIC section of DE42u1.

3) I put a value in that variable, before calling the dialog.

4) Within the dialog that variable was available from DE42f1.wTest

(End of Aside)

======

Almost there!

The only thing left is to talk a moment about my "line wanted" criterion.... one that you will probably replace with one of your own, relevant to your data, your needs.

The lines in my file are all date-stamped. A typical line is....

t208h24a05+3022

In that line, the first two characters tell us what sort of reading the line holds. The next 7 characters give a date and a time, the "datestamp", by which I mean in this to imply date-and-time stamp. You don't need to worry about how it is coded.

Characters 3-9 almost always hold a datestamp. The only exception is lines beginning cm (for "CoMment"), which can have anything after the "cm". (The program will treat any line beginning with a "c" as a "cm" line, which, given the data, is okay.)

The lines in the file are always in chronological order.

When I run the application, my criterion for "the" line from which I start copying will simply be "Is this a line after a particular date/time?" That sounded so modest when I wrote it, hours ago during the production of DE41. As I said then, this essay isn't (sorry) primarily for you. It is a byproduct of my creation of the application that I actually need still, at the time I write this. Why do I suspect that my "simple" little criterion is going to give me a headache? We'll see. In the simple version of this application, produced for this tutorial, I kludged things... a perfectly respectable way to build an application in controlled stages. Now it is time to move on to doing what is actually wanted. I only need to refine the boLineIsWanted function, which I've done. You can see the result in the sourcecode.

In case you didn't read the first essay, the introduction to this one, let me repeat something....

Note I said "date after which...", not "date on which..."?

It is usually wiser to use such criteria. Suppose I said that copying should start when the record date is, say, 10 December 2007. What if the computer was down on the 10th, and there were no lines for that day? It is much safer to say "Copy everything created after (and including) 9 December." If there were lines from the 10th, they'll get copied. If there were no new lines after the 9th until, say, the 12th, the program, written as I have done it, will work fine. Make your criteria "is it more than?" or "is it less than?", and use "is it equal to?" only in certain cases where that is necessary.

Another "detail": Note I said we would be copying all lines from and including the first line with a datestamp after the 9th? In this case, missing one line would hardly matter, but in many cases it would matter considerably... and it is easy to program things so that the first line (or the last) is missed out.

(End of block of repeated text.)

That's it! Sorry for the abrupt end!


To search THIS site....
Click this to search this site without using forms, or just use......
powered by FreeFind
Site search Web search
Be sure to spell the words you are searching for correctly!
The search engine doesn't understand English. Searching for "How do I use repeat" will just return pages with "how", "do", "I", "use", or "repeat".
(Go to my other sites (see bottom of page) and use their search buttons if you want to search them.)...


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!!! Sheepdog Software (tm) is supposed to help do that, so if you found 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
How to email this page's editor, Tom Boyd



Valid HTML 4.01 Transitional Page 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 .....