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

Delphi: Development of a Program

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.


Welcome!

This tutorial is a chance to sit at my elbow as I put a program together. It will not contain as much "now type this..." information as a typical tutorial from this series. Not every detail will be explained for you. Several topics on which this is based are discussed in more detail in other tutorials... used the search button, maybe? (E.g. File handling, graphics programming.)

At one point, I had a zip file with the necessary sourcecode. That is for the moment lost (Compuserve discontinued its web hosting, and just dumped what I had on their server. Of course, that is no exuse for the lapse in my local backup procedures.) I will try to replace the zip one day. Anyone really interested?

The tutorial is, at present, in a "first draft" state. After three days, frankly, I'm sick of it! I'll try to come back to it another time. For now, apologies for the typos, etc. I'll refund what you've paid me for it to anyone submitting their receipt.

The program is to display data collected about access to a storeroom with a computer connected to its door logging when the door is open. Note: This program only looks at a data file created by the computer connected to the door. This program doesn't do any of the data collection.

The data file is in simple ASCII. The following is the start of a typical file. the REM: statement is hand entered, and all such will be ignored by the graphing program. The other statements are computer generated. Eventually, all will contribute to the graphic display.
REM: Log file restarted mon 29 July 02

LA16, ver 29/7/2002 restarted at 17:21 on (DMY) 29-7-2002
Switch was open.
Closed: 17:21 on (DMY) 29-7-2002
Opened: 17:23 on (DMY) 29-7-2002
Closed: 17:23 on (DMY) 29-7-2002
Opened: 17:23 on (DMY) 29-7-2002
Closed: 17:23 on (DMY) 29-7-2002

LA16, ver 29/7/2002 restarted at 17:24 on (DMY) 29-7-2002
Switch was closed.
Opened: 17:24 on (DMY) 29-7-2002
Closed: 17:25 on (DMY) 29-7-2002
Opened: 17:25 on (DMY) 29-7-2002
Closed: 17:25 on (DMY) 29-7-2002

LA16, ver 29/7/2002 restarted at 17:27 on (DMY) 29-7-2002
Switch was open.
Closed: 17:27 on (DMY) 29-7-2002
Opened: 17:27 on (DMY) 29-7-2002
Closed: 17:27 on (DMY) 29-7-2002
Opened: 17:27 on (DMY) 29-7-2002
Closed: 17:27 on (DMY) 29-7-2002
Opened: 17:28 on (DMY) 29-7-2002
Closed: 17:28 on (DMY) 29-7-2002

LA16, ver 29/7/2002 restarted at 17:46 on (DMY) 29-7-2002
Switch was closed.

LA16, ver 29/7/2002 restarted at 17:46 on (DMY) 29-7-2002
Switch was closed.
--------day changed at 0:0 on (DMY) 30-7-2002
--------day changed at 0:0 on (DMY) 31-7-2002

LA16, ver 29/7/2002 restarted at 8:17 on (DMY) 31-7-2002
Switch was closed.
--------day changed at 0:0 on (DMY) 1-8-2002
--------day changed at 0:0 on (DMY) 2-8-2002
--------day changed at 0:0 on (DMY) 3-8-2002
Opened: 19:57 on (DMY) 3-8-2002
Closed: 20:01 on (DMY) 3-8-2002
--------day changed at 0:0 on (DMY) 4-8-2002
The "----day changed..." records are present to confirm at least once a day that the computer is still running, i.e. not crashed through power failure or Windows "feature".

Initially, the program will plot a "-" for each "day changed", a "^" for each "opened", a "v" for each "closed", a ">" for each "restarted". It would be nice if the graph had lines to show when we can presume a state for a given period, i.e. a "closed" line for all of 2 August 02... but that would require a much more sophisticated program. Our initial plans allow each record (line) of the log file to be processed in isolation, with no need to "connect" events.

If there were no "open" or "close" events, the graph would look a little like...

-___-___-___-___-___-___-___

-___-___-___-___-___-___-___

-___-___-___-___-___-___-___

... each line is the record for a week. The bottom line is the most recent week. The hyphens in the above are the "day changed" reports.

In broad terms, the program will consist of the following:

A) Prepare a 'blank page' of graph paper. During this process, various values needed subsequently in the program will be calculated and stored for future reference. (Details later.) (In "FormCreate" part of program.)

B) Look for, and open if found, the file of data. If found, also determine first date, last date in file. Check the format is right for this program. (In "CheckFile" part of program)

C) Ask user for the time period of interest, and from that and information obtained previously determine first and last dates to be plotted. (Handled by edit box event handlers, with some help from "CheckFile".)

D) Read file from end to oldest record of interest. (I am writing the program to read backwards through the data because I presume that it will be recent data that is most often requested. This approach avoids long reads forward from the start of a file. (Part of "DoGraph" part of program.)

E)
Set boDone:=false;
Repeat (*1*)
Plot record;
bRecordType:=0; (*rogue for "Useless to us"*)
Repeat (*2*)
Try to read prior record, moving pointer back in process;
If no prior record, then boDone:=true;
Fill bRecordType;
If suitable recordtype, put records DateTime in dtTmp;
If not boDone then if record pre-dates period of interest, then boDone:=true;
Until bRecordType<>0 or boDone; (*2*)
Until boDone;(*1*)
Close file.
(All of E part of "DoGraph" part of program.)

Sounds simple enough... let's get started....

==========
(By the way: Beginners: I have a much clearer, much more detailed idea in my head of all of the above, and most parts of it I have done before in other programs. You can never spend too much time planning in advance what a program is going to do. I am pressing ahead thus a) because I have bad work discipline, b) to keep this readable.)

I'm calling the program GP2, and will first create a "shell" into which the various elements can be fitted.

The shell will have a form with an image created during the form's onCreate.

Eventually, there will be a way to specify which data file you want to look in. For now, it will look in a specific file in the local folder when the user clicks on the "Chk File" button. The form will have labels to display the earliest and latest data in the datafile.

Once the file has been opened and checked, the user will be able to alter the setting in some edit boxes for start and end dates. (The dates of the period to be shown on the graph.) (Editing before the data file will not be possible because there will be checks to see that there is data for the dates selected.) If you edit one of the dates, the computer will show you what the other date will be. (This is going to be a pain, because each line of the graph will start on Monday, so it won't be a simple "calculate the date 21 days forward (back)" for a three week graph. (As if even that would be simple!) Once suitable dates are established, and the data file has been opened up, checked, a "Do Graph" button will be enabled. Click the button, get the graph! (The setting of the graphing range is not complete at the time this tutorial is being published... but parts of the above work!)

Note that the enabling of "Do Graph" is going to be complex, relying on two Booleans. This is because once the datafile can be changed, provision must be made for situations where a user goes back and changes the data file after specifying some dates. The Booleans will be boFileReady and boPlotDatesSet. (As the programming progressed, I realised that these were not being used as much as I'd thought they would be.) Note that buDoGraph and the dates-to-plot edit boxes start in the enabled=false state.

When I had completed the initial form design, and just a little of the program's coding, I saved what I had in the folder "StateA", so have a look at that, if you want to see how the project began.

===
I'm going to start with the CheckFile part of the program. There's more detail on reading datafiles in my datafile tutorial.

To make it easy to add a "Choose which file" option later, the name of the file to be opened will be in a variable, sFileName.

All of the following were written to support parts of CheckFile, but they will be useful later in the program, too. Ah, the joys of breaking things into general purpose procedures and functions! Note that some of them are quite intolerant of the format of the records in the datafile. ReadLineForward, ReadLineBackwards, DecodeRecord, GetNumberFromString, TimeStrToParts, DateStrToParts. Note that each was painstakingly de-bugged as it was added to the program. One of a programmer's skills is finding ways to debug as bits are added, not in one nightmare at the end when there are all sorts of untested things clashing with one another.

This portion of the program proved tedious, with a lot of string manipulation in order to transform dates and times expressed as strings into dates and times held in word variables for day, month, year, hour, minute. Delphi's EncodeTime and EncodeDate were useful for turning the date/time specifications into the internal code used in Delphi for DateTimes. In that code, the day determines the whole number part of the DateTime value, and the hour of the day determines the fractional part of the DateTime value. A few examples....
0.0 is midnight on Dec 30, 1899
0.5 is noon on Dec 30, 1899
36526 is midnight Jan 1, 2000
(N.B. Delphi 1 used a similar system, but day 0 was in year 0001.)

The Delphi way of storing DateTimes will make finding the right place on the DateTime axis of the graph easy. You no longer have to worry about the number of days in September, nor where leap years fall.

Once the "Check (and do lots more with it)File" procedure was working, it made sense to at least get a rough version of part "C" of the program working. That's the part that gets and checks the user's wishes as regards to the range of dates to be plotted. At this stage, a start was made, using the edit boxes and their handlers. The job wasn't finished at this point, as some of the "will these values work?" tests, and some of the automatic generation of the other bound of the range could not be done until the "create blank sheet of graph paper" work is done. However, a start was made, and some enabling/ disabling of the buDoGraph was programmed.

Features to check out include...

Use of one event handler to cover multiple controls (eGDSD,eGDSM and eGDSY share eGDSDExit)
Use of bMostRecentlyVisited. (I think this could be done with something more clever.... Perhaps var oMostRecent, oMostRecent:=self, with oMostRecent do setfocus?... or something along these lines? but my more transparent system works. "Bad" in that simple typos in the assignments to bMostRecent would confuse the system.)
Interlocking OnKeyPress/ OnKeyDown events and enabling of buDoGraph.
Use of Try... Except... End in eGDSDExit.
Use of EConvertError in eGDSDExit.

I haven't saved the state of the program at this point because what comes next is more or less independent of the above.

Once the file was open, and control of the range of dates to be plotted at least started, rather than go directly to the graphing of the data, I wanted to implement parts "D" and "E", but with the numbers to be plotted being displayed as numbers when the program worked past that part of the data file. I did not want to start struggling with turning the numbers into a graph until a sensible stream of numbers is being generated!

When I had got this far with the program, I saved the sourcecode again, in the "StateB" folder.

===
Now to add the graph drawing.....

See my (written but not yet published... Pester me if you want it!) earlier tutorial on re-sizable windows, if things in what follow are too obscure.

First I set up the basics in the OnResize event, code which prevents the window being made too small, and code that keeps changing the image control's size to suit the current size of the form.

Note the bit of bad programming "necessary" due to my ignorance of something I've yet to get to the bottom of: I set up two "constants" in OnFormCreate...
wImagePicHeight:=285;
wImagePicWidth:=285;
These are set to 285 because, using the RAD IDE, I made the image 285 x 285 in the design phase. If the size of the image is changed, the values in wImagePicHeight and wImagePicWidth need to be changed. I think that both the control's size, and the size, in pixels, of the underlying object you draw on (image1.canvas) are set during the design process. Once the program is running, the control's size can be changed, and if you remembered to set the image property "stretch" true, whatever's on the canvas will fill the control's area. However, even though you are changing the number of pixels used, you are not changing the number of "pixels" in the virtual canvas inside the machine. With the figures above, lineto(142,142) will always draw a line to the center of the image, regardless of its current size.

Sadly, the "stretch" mechanism in Windows (or is it Delphi?... anyway: it is beyond your reach!) doesn't work entirely as I would wish. Suppose you had five horizontal lines on the image. If you re-size it smaller, not all five lines remain visible at all zoom levels. Another approach might give better results, but the programming overhead would be large, and execution time for the re-size might become unacceptable.

I couldn't see the image until I put a moveto(10,10) in OnFormCreate. Eventually, that was replaced by something more useful, but don't worry about why you see no image until something is done to/ with the image.

We now have a drawing area. If we say...
image1.canvas.moveto(0,0);
image1.canvas.lineto(140,140);
... we will get a line from the upper left hand corner to nearly the middle of the drawing area. If, while program is running, we re-size the form to a bigger size, the line will now be more pixels, but it will still go to near the center of the drawing area.

"Forget" the details of the program's internals for a moment, and try to fix the overall requirements in your mind, and the details of their components:

The program is going to generate several rows with 7 columns in each. Each cell is going to display events from a single day. The programming will be organized in several layers

While it might not be the most obvious approach, for a variety of reasons, I decided that the way to approach the project was to think of the picture as building from the lower right, doing a row at a time. I.e. in the reverse order that we would normally use in reading.

The position of the table of cells will be the net effect of a programmer chosen RIGHT margin, a BOTTOM margin, and the number of pixels chosen for the width and height of each cell. Again, not the usual way of formatting something, but it worked well.

Almost all of the relevant numbers (e.g. number of pixels for the margin on the right) are placed in variables, and then the contents of those variables are not changed. Thus, they are being used as if they were constants. Some of the numbers go in true constants.

The benefit of this can be illustrated by a trivial example. Suppose you want to change the size of the right margin? Say it was 5 pixels initially. If you used a 5 each place there was need, changing the margin becomes a pain fraught with chances for error. If you used bRtMargX:=5 and then put bRtMargX each place there was need, then "re-programming" the program for a different margin is almost trivial.

A little trick for you: Assuming for the moment that bRtMargX is one of these constant "variables" (or, indeed, a true constant), then if you are going to want to do something like....
x:=wCell1x-bRtMargX+1
... especially if it is going to happen in an oft repeated loop, it pays to put the following somewhere early in the program:
bRtMargXPlus1:=bRtMargX+1
Then your x assignment becomes...
x:=wCell1x-bRtMargXPlus1
... and (in crude terms) you've reduced the calculation overheads by 33%.

Moving on....

How do we get from the data to the picture? Think again about the sort of data involved.....
LA16, ver 25/7/2002 restarted at 1:21 on (DMY) 26-7-2002
Switch was open.
--------day changed at 0:0 on (DMY) 27-7-2002
Opened: 7:23 on (DMY) 27-7-2002
Closed: 17:21 on (DMY) 27-7-2002
--------day changed at 0:0 on (DMY) 28-7-2002
Opened: 17:23 on (DMY) 28-7-2002
--------day changed at 0:0 on (DMY) 29-7-2002
Closed: 17:23 on (DMY) 29-7-2002
That can be thought of as a wordy version of....
Restart 0:0,   27-7-2002
DayChg  0:0,   27-7-2002
Opened: 7:23,  27-7-2002
Closed: 17:21, 27-7-2002
DayChg  0:0,   28-7-2002
Opened: 17:23, 28-7-2002
DayChg  0:0,   29-7-2002
Closed: 17:23, 29-7-2002
Dates! Woe is me! "Thirty days hath September..." They are a REAL PAIN.

Happily, Delphi has good tools for working with dates. January 1st, 2000, in Delphi's internal representation, is day number 36526. (Beware: Delphi 1 used a very similar scheme, but with a different base date.) Very sensibly, they allow you to specify a certain time on a certain day by using the fractional part for the time. Midnight 1/1/2000 is 36526.000. Noon 1/1/2000 is 36526.500. Midnight Jan 2, 2000 is 36527.000, and so on. Don't worry... there's a whole host of built in functions and procedures to convert back and forth between Delphi DateTimes and human dates and times. Start exploring the help file at EncodeDate.

The program calls procedure SetConst just once. It is here that the various constant "variables" are assigned the values we want in them.

The program calls procedure PrepImage to draw the lines which appear on every display generated by this program. They are horizontal lines along the bottom of each week's row, with small vertical lines to show where midnights occur.

At the start of buDoGraph's handler, the program looks at a few things it already knows like the last date to be shown on the graph and the day of week the programmer has selected for the first column. During this process other constant "variables" are given values.

When the data is processed, a line like "Opened: 7:23, 27-7-2002" is converted to:
a code for the event to be reported on the graph.
a number for which day column the datum should go in
a number for which week row the datum should go in
a number for how far across the day's cell the datum should go.

This was quite a bit of work!

The payoff came because once the record had been converted to the 4 data, a procedure called PlotSymbol became easy.

Within PlotSymbol there are four routines, DrawCaret, DrawPtRight, DrawVee and DrawDot. Each puts a symbol at the x/y location specified by PlotSymbol. PlotSymbol takes care of combining all of the things like the size of the drawing area, the margins required, which day/ week the datum relates to, the width of columns, etc, etc, and producing the x/y values used by PlotCaret, etc.

... And that just about does it! You'll find the final code in the folder StateFinal, and with it a NEW, bigger file of test data.

Enjoy. Please remember that the application that this is based on is copyright, TK Boyd, 9/02. You are welcome to study the sourcecode and learn things, but you'll be chancing problems if you try to distribute anything too closely resembling my program. If you want to buy a license to use the program, or something similar I could produce to your requirements, don't hesitate to get in touch!! Tell me a little about who you are, where you want to use it, for what... (I can also supply the program that produces the datafile. That runs in a DOS box.)

==== P.S......

Here are two things I've discovered while writing a derivative of GP2. They don't really belong here(Pester me to give DD27 it's own Tut!), but maybe you've found them anyway with my site search engine, thank you FreeFind. Before I start on them, two even smaller points: 1) bTmp:=bTmp+1 may be better than inc(bTmp), because range checking isn't applied to the latter, even with {$R+} and 2)
image1.canvas.pixels[10,20]:=clRed;
Makes the pixel 10 to the right and 20 pixels below the upper right hand corner of image1 red.

On to the larger points...

a) You need an application.processmessages from time to time in the following, or you don't see any points plotted until the loop finishes. Of course, you should have application.processmessages calls within any long loop anyway. Don't be distracted by the fact that I've used a repeat...until loop to make a for...to... loop. I know what I'm saying is true with repeat...until, and am only guessing that the same would happen with a for...to... For example, the following....
liTmp:=0;
repeat
image1.canvas.pixels[random(50),random(50)]:=clRed;
liTmp:=liTmp+1;
application.processmessages;
until liTmp=4000;
... would work nicely, whereas without the application.processmessages, you wouldn't see the pixels until the loop finished. (Of course, for just a few pixels, the delay wouldn't matter, but I recently wrote a program which prints tens of thousands of pixels within such a loop.)

Without the application .processmessages, the loop finishes MUCH more quickly. You may want to incorporate the following compromise....

Before loop:
bTmp2:=0;
Inside loop:
bTmp2:=bTmp2+1;
if bTmp2>100 then begin
  bTmp2:=0
  application.processmessages;
  end;


b) I don't often use menus as my programs usually only need a few "Do It" buttons to meet my needs. I almost always have a "Quit" button. Usually, the OnQuitCLick handler is simply application.terminate. However, this does not always kill programs for me. Recently, I've begun to suspect that the following may be what's needed when the program is in a loop....

Before the loop:
boDone:=false;
In the OnQuitClick handler, and in the main form's OnClose handler:
boDone:=true;
An additional condition with the repeat:
or boDone


            powered by FreeFind
  Site search Web search
Site Map    What's New    Search This search merely looks for the words you enter. It won't answer "Where can I download InpOut32?"
The search engine is not intelligent. It merely seeks the words you specify. It will not do anything sensible with "What does the 'could not compile' error mean?" It will just return references to pages with "what", "does", "could", "not".... etc.

Also: This search only searches the material on one of my websites.

      Click here to visit another of my sites.

      Click here to visit my third site.




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. I 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 a Windows or MS-DOS 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 .....