HOME - - - - - - - TUTORIALS INDEX - - - - - - - - - - - - Other material for programmers

Delphi: One of the better tutorials in this series at 9/06

General points, along the way of a discussion of...

This tutorial shows you several useful tricks in a somewhat artificial context. You can read it for those tricks, or you can tweak what is presented to turn it into something addressing needs you have.

It is not a short tutorial, but it is one I particularly commend to you. For a start, I've lavished more editorial time on it than on a typical tutorial. It covers several points of general use and importance. I wrote it a long time after beginning the tutorials series, and you get the benefit of the hours I've spent working with Delphi since writing some of the early tutorials.

Beware of getting half way though and thinking, "Okay, I've got that." Several new things crop up in places you might not anticipate them.

The tutorial covers having multiple forms in an application, and it covers passing Delphi controls to subroutines. It also illustrates using boolean variables to manage the appearance of a form. The way they are going to be used means that different things can happen at different times... times the programmer can neither anticipate nor control. If you are a dinosaur programmer, who started back in the days of structured programming, and who is used to sequential execution, there are some wonderful things you can do in event driven programs that just can't be done "the old ways". Here you can learn about doing those things. If you aren't from "the good old days", you may not even see that what we're going to do is wonderful... but it is!

The tutorial arose from an application I wrote in connection with my FarWatch system. FarWatch costs nothing more than having an always-on internet connection. It doesn't have to be a static IP address. With it, you can monitor a location across the internet. The simple FarWatch merely puts a page of information on the net for anyone to check. The application that this tutorial derives from automatically reads that page from time to time, and displays reports for the user. The reports are displayed where ever the FarWatch monitoring program is running, which can be on any internet connected Windows PC. The reports are only on-screen messages at the moment, but it would be relatively easy to hook the computer up to an alarm bell which could be made to ring.

The reports (seen at the place where the FarWatch monitoring program is running) come in two flavors: Alarms and warnings. A warning might be "The watched house's temperature has fallen to 50 degrees F." Bad news, but not worth ringing an alarm bell at 3am! An alarm might be "The intruder detector reports someone in the house." or "The house's temperature is now 35° F and falling. The water pipes may freeze soon." Worth ringing that bell, even at 3am!

In this tutorial, instead of looking at a FarWatch page, the program will merely look at a scrollbar object. If its "position" property returns a low enough value, there will be neither warning nor alarm. If the thumb tab is higher, a warning will arise. If it is very high, an alarm will arise. In either case, the program will return the slider to the "no warning/ no alarm state" when it detects either. (This to prevent multiple warnings/ alarms.) You could re-program the application to watch different inputs. Connecting things through a parallel port is explained on my parallel port page. (I also have a serial port information page.)

Inside the zip file with the project's source code, you will also find the exe. This would be a good time to run that, to get an idea of where we are going. (It won't "do things" to your system, e.g. write to your registry. Or contact the internet.) Move the scrollbar's thumb tab to half way across; release the mouse button. Move the thumb tab again, this time all the way to the right.

What I've described above could be accomplished many ways, all of which would look the same to the user. One mark of a well designed program is that it is easy to modify it. I hope the tutorial's program falls into that category. If so, all sorts of refinements are relatively easy. Take, for instance, the alarm bell. In a simple application, it would ring whenever an alarm condition existed. In a fancy program, it could be make to ring briefly once in a while IF the time of day was between 8am and 10pm, AND there a warning condition existed.

If the program is well designed, a further frill should be easy to add: It should be possible for the hosting computer to go into a monitor-off standby mode, but wake if there is a warning or alarm. See the screensaver reference in the table of contents of my tutorials if you want to implement this frill.

And so on! One of the great joys of programming is that the only limit is your imagination!


The application will have three forms.

The main form will have sundry buttons and displays. The other two forms will each have a memo. One will display the warnings the application has seen, and the other will display the alarms. It is relatively easy to extend the application to record the warnings and alarms in data files, should you want that frill.


Start a new application. I am calling this application DD68 (68th Delphi Demo). Name the form DD68f1.

Put a scrollbar on the main form. This is just a quick, easy, uncomplicated source of artificial alarm and warning conditions. Leave everything at their default settings. (Those should include: Position=0; Min=0; Max=100) (Project developed using Delphi 2. There is noting "clever" in this that should not work in later versions of the IDE.)

Put a timer on the main form. If the scrollbar was going to be what we wanted to use in a real application, we wouldn't need this timer, but for most things, you will want to use a timer. It lets you check things periodically, without having to depend on anything else to trigger the check. Again, the default property values are fine.

Save what you have so far, calling the unit DD68u1.pas. I recommend a separate folder for every Delphi project.

Double click on the timer, to open the OnTimer event handler. Make it...
CheckAndHandleAlarmsOrWarnings;
Within the private part of the TDD68f1 class declaration, add....
procedure CheckAndHandleAlarmsOrWarnings;
Just before the final "end.", add....
procedure TDD68f1.CheckAndHandleAlarmsOrWarnings;
begin
if scrollbar1.position>60 then begin
       scrollbar1.position:=0;
       showmessage('Alarm!!');
       end;
end;
... and get that much working. (If you move the scrollbar thumb tab to the right, you should get a message saying "Alarm!").

After the ....
      showmessage('Alarm!!');
      end;
....you just inserted, but before the procedure's "end;", add....
if scrollbar1.position>30 then begin
       scrollbar1.position:=0;
       showmessage('Warning!!');
       end;
Now you should get warnings, too. Note that you will sometimes get a "Warning" during the instant you are moving the thumb tab up to an "Alarm" state. This is just a quirk of the way we are generating warnings and alarms. It occurs if the timer event happens at the precise moment that you are moving the thumb tab through the "warning" zone. Real sources of warnings and alarms are not likely to behave thus.

Before we go back and refine the Warning and Alarm handling, we are going to make separate forms (windows) to hold logs of the warnings and alarms received to date. Each window will have a memo on if for that job. The memos could be on the main form, but it isn't as easy to manage them if they are there. As separate windows, users can put them where they wish.

Click File | New | Form (Click OK)... twice. Re-save the project, calling the new units DD68uWarnings and DD68uAlarms.

Name the forms DD68fWarnings and DD68fAlarms, put a memo on each, resize and reposition both form and memo so that all three forms are visible.

Set the "visible" property of the two new forms to true, using the Object Inspector (open with F11).

In the FormCreate handler for each of the new forms, add...
memo1.clear;
Just for the sake of going a step at a time, just before "showmessage('Warning!!');" add....
DD68fWarnings.memo1.lines.add('Hello Window!");
Save everything. Run the project.

When you try to run the project, you'll be told....

"Form DD68f1 references DD68fWarnings"

.. and asked...

"Do you wish to add it [to your USES clause]"

Say yes.

Move the scrollbar's thumb tab up into the "Warning" zone, and look for the "Hello Window" that should appear (along with the as-before ShowMessage output.)

===
Pause for breath.

===
Don't worry about "Why do it that way? You could also...." Yes! I know! (For instance, the memos could be on the main form. You'd just have to give them different names.) This tutorial isn't trying to show you "the" way to monitor some alarm system. It is merely illustrating some things.

===
Moving on.

===
We're now going to "divide", in hopes of "conquering". For the moment, in respect of what follows.... "Just do it!" At first it may seem pointless, but when you get to "Review the above", review what you've done, looking back to be sure you understand all that has happened.

The first changes below are merely the first steps along a path.

===
Within the private part of the TDD68f1 class declaration, add....
procedure ReactToWarning;
... and change the bottom part of your program. Note that you will be re-doing bits of CheckAndHandle in addition to adding new stuff. Make the last part of your program....
procedure TDD68f1.CheckAndHandleAlarmsOrWarnings;
begin
if scrollbar1.position>60 then begin
       scrollbar1.position:=0;
       showmessage('Alarm!!');
       end;
if scrollbar1.position>30 then ReactToWarning;

end;

procedure TDD68f1.ReactToWarning;
begin
       scrollbar1.position:=0;
       DD68fWarnings.memo1.lines.add('Hello Window!');
       showmessage('Warning!!');
end;

end.
Save and run the project. It should behave as it did before.

Take out the "showmessage('Warning!!');". It is no longer needed, and by not having a showmessage, we've made a program that can run unattended for long periods. Any messages will still be on show, but no one has had to click "OK" to make a message box go away. You can also put "save this warning (or alarm) message to a data file" code into the React... procedures... something not provided for in a mere "showmessage".

It is possible that if you extend the program, there may be other warnings (arising elsewhere) that you wish to present to the user, so we are going to modify "ReactToWarning". Change the call to....
if scrollbar1.position>30 then ReactToWarning('Scrollbar Position');
Change the declaration to....
procedure ReactToWarning(sMsg:string);
... and make the implementation....
procedure TDD68f1.ReactToWarning(sMsg:string);
begin
       scrollbar1.position:=0;
       DD68fWarnings.memo1.lines.add('Warning seen at '+
                DateTimeToStr(Now)+
                ' '+sMsg);
end;
Save. Run. Debug.

Confession: That "scrollbar1.position:=0;" is a little kludgey. But not a big deal, so don't worry. It's just that it is too specific. Code should strive to be general, flexible. If you change the mechanism by which a "warning" condition arises, and the "scrollbar1.position:=0;" may become irrelevant, but the fact may not be noticed, and that odd little line of code may perplex you hours later when you stumble across it and try to remember what it is doing there.

(The "DateTimeToStr(Now)" item is all taken care of for you by Good Old Delphi. It uses two built in functions, "Now" and "DateTimeToStr". The format of the string will vary from system to system, as it responds to how Windows has been configured on the system in question.)

This is the time to review the above; this is the "review it" point I promised earlier.

===
Add...
procedure ReactToAlarm(sMsg:string);
... to the declarations. Add...
procedure TDD68f1.ReactToAlarm(sMsg:string);
begin
       scrollbar1.position:=0;
       DD68fAlarms.memo1.lines.add('Alarm seen at '+
                DateTimeToStr(now)+
                ' '+sMsg);
end;
... just before the unit's final "end.", and rewrite...
procedure TDD68f1.CheckAndHandleAlarmsOrWarnings;
begin
if scrollbar1.position>60 then ReactToAlarm('Scrollbar Position');
if scrollbar1.position>30 then ReactToWarning('Scrollbar Position');
end;
Save. Run. Debug. (Once again, you'll add a reference to the Uses clause.)

You may find that you get "extra" alarm or warning reports if you drag the scrollbar thumb tab into, say, the warning zone, and linger with the mouse there. The extra warning comes if you release the button too slowly.

You can give the memos scrollbars, by the way. Use the Object Inspector.

===
I think it would be nice if the errors and warnings were numbered, don't you?

On the line after "{Private declarations}", add...
iNumWarnings,iNumAlarms:integer;
Just before the program's final "end.", add...
procedure TDD68f1.FormCreate(Sender: TObject);
begin
iNumWarnings:=0;
iNumAlarms:=0;
end;
Alter ReactToWarning, making it...
procedure TDD68f1.ReactToWarning(sMsg:string);
begin
       scrollbar1.position:=0;
       inc(iNumWarnings);
       DD68fWarnings.memo1.lines.add(inttostr(iNumWarnings)+
                ': Warning seen at '+
                DateTimeToStr(now)+
                ' '+sMsg);
end;
.. and change ReactToAlarm the same way.

=====
Next... something really tricky. Well, it seems so to me... but that's probably because I'm self taught. I wish I'd stumbled on what follows a long time ago, as I think it is going to prove very, very powerful.

Given the level of this tutorial, I hope you are quite comfortable passing parameters to subroutines? We've done a bit of it in this tutorial already. However, did you know that things like memos can be passed to procedures? An example follows.

Instead of having....
DD68fAlarms.memo1.lines.add(inttostr(iNumAlarms)+
                ': Alarm seen at '+
                DateTimeToStr(now)+
                ' '+sMsg);
and
       DD68fWarnings.memo1.lines.add(inttostr(iNumWarnings)+
                ': Warning seen at '+
                DateTimeToStr(now)+
                ' '+sMsg);
in our program, we're going to create a WriteToMemo procedure. It will still need all of the "stuff" that we had after the "lines.add(...", but it is still worth having. You'll see. (I hope.)

In the declarations, add....
procedure WriteToMemo(var meWhichMemo:TMemo;sMsg:string);
... and, just to make a start, to ReactToAlarm, just after the existing "DD68fAlarms.memo1.....+sMsg);", add...
WriteToMemo(DD68fAlarms.memo1,'hi');
The program should run. (If it doesn't, be sure you put the "var" in after "WriteToMemo(". Very important. It means that within the procedure, you deal with whatever memo you referenced... the actual memo... not a COPY of it, which would be the case otherwise. (For example, we only made a copy of the "hi", which was passed as the second parameter.))

Once that's working, combine the two lines which write things to the memo, making them just....
       WriteToMemo(DD68fAlarms.memo1,
                inttostr(iNumAlarms)+
                ': Alarm seen at '+
                DateTimeToStr(now)+
                ' '+sMsg);
Make a similar change in ReactToWarning, to bring it in line with ReactToAlarm.

Presto! You've made the program more advanced. It still does the same things, but does them better. "Better" because the details of writing to a memo... which you do at least twice... are all "wrapped up" in one procedure. If you want to change how you write things to memos, you make the change in the procedure. Thereafter, any writing of memos, regardless of what instance of that you are concerned with, works consistently. If you'd left the writing- to- memo at the mercy of the repeated fragments of code, you would almost certainly at some point modify one instance, but forget to modify one of the others.... and then wonder why your program didn't perform consistently.

Not an aside . . . . .

Originally, I wrote the following as an aside, but then I realized that even if you don't incorporate the code, it illustrates an important technique. Note the "if meWhichMemo=DD68fWarnings.memo1 then...." in the following, and think about what it is saying.

Suppose you wanted to save the reports of warnings and alarms to datafiles, two datafiles: One for the warnings, one for the alarms.

It would be fairly simple to implement that feature because of the WriteToMemo procedure. (It would be best to rename it to something like WriteToMemoAndDatafile.)

I said "fairly simple". It would be necessary to add code elsewhere to open the datafiles when the program started, and close them when it ended. The code isn't complex, but getting it in just the right places, with all the necessary safeguards against user actions is tedious. I'm not going to go into that now. However, were that done, all you'd have left would be to add the following to WriteToMemo....
if meWhichMemo=DD68fWarnings.memo1 then writeln(dfWarnings(sMsg));
if meWhichMemo=DD68fAlarms.memo1 then writeln(dfWarnings(sMsg));
The clever thing to notice is that we are asking which memo the procedure has been called to write to. Doing something like "if c1=5 then..." is probably old hat to you, but when did you last ask if a variable was holding one of the objects or controls from your application?

It has taken us a while to build the WELL BUILT program we have... but it was worth it, because it is so easy to add things to a well built program.

[End of Not an Aside]

Moving on....

I promised you that this program would also illustrate using booleans to control appearances, even when the programmer can't tell the order things are going to happen in. Along the way, you're going to get another example of passing controls to a subroutine.

The program's new behavior will be as follows....

When an alarm or warning arises, the relevant memo will flash attention-getting colors until a user clicks on a "Warning acknowledged" or "Alarm Acknowledged" button.

To implement all of that will require several modifications. You won't see the results until near the end.

Before we move on to that, let me mention that if you haven't heard about state diagrams as a way of planning a system, you might want to try my introduction to them. (Clicking on the link will open that in a new window, so you'll be able to resume this afterwards.) They are effective in overcoming the shortcomings of flowcharts in an event driven environment.

Going back to our current project: Proceed by adding "Warning acknowledged" and "Alarm Acknowledged" buttons to the main form. Call them buAckWarn and buAckAlarm.

Just after "iNumWarnings,iNumAlarms:integer;" in the declarations, add....
boWarningAckd, boAlarmAckd:boolean;
.. and in the FormCreate handler, set them both true.
boWarningAckd:=true;
boAlarmAckd:=true;
In ReactToWarning, add....
boWarningAckd:=false;
Yes: false. (You have not yet acknowledged the warning, have you?)

And add similar to ReactToAlarm.

Make the handle for the buWarningAckd clicked event....
boWarningAckd:=true;
MakeNormal(DD68fWarnings.memo1);
... and do similar for buAlarmAckd.

To CheckAndHandleAlarmsOrWarnings add....
if boWarningAckd=false then Flash(DD68FWarnings.memo1,clBlue,clYellow);
if boAlarmAckd=false then Flash(DD68FAlarms.memo1,clRed,clWindow);
To procedure TDD68f1.buAckWarnClick, and buAckAlarmClick, add...
MakeNormal(DD68fWarnings.memo1); //for WarnClick
MakeNormal(DD68fAlarms.memo1); //for AlarmClick
... and it should all Just Work.

Oh! Sorry! I guess it will work better when you've added the declarations and implementation code for Flash and MakeNormal.....
procedure TDD68f1.Flash(var meWhich:Tmemo;cl1,cl2:Tcolor);
begin
if meWhich.color=cl1 then begin
   meWhich.color:=cl2;
   meWhich.font.color:=cl1
   end// no ; here
 else begin
   meWhich.color:=cl1;
   meWhich.font.color:=cl2
   end;//else
end;

procedure TDD68f1.MakeNormal(var meWhich:Tmemo);
begin
   meWhich.font.color:=clBlack;
   meWhich.color:=clWindow;//clWindow will make the background
       //be whatever color the user has selected for window backgrounds
       //across all of his/ her desktop.
end;
Whew! I'm exhausted. What about you?


            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?"
Please visit the following to help make my site better known...

        Top 100 Borland


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
How to email or write this page's editor, Tom Boyd


Why does this site cause a script to run? I have the traffic to this page monitored for me by Jazar Top 200 Delphi, and they provide promotional services, too. Click the "Help get this site publicity" link above for more information. Why do I mention the script? Be sure you know all you need to about spyware.
{end}