HOME - - - - - - - TUTORIALS INDEX - - - - - - - - - - - - Other material for programmers
Delicious.Com  Bookmark this on Delicious     StumbleUpon.Com Recommend to StumbleUpon

Delphi/ Lazarus: An illustration of how close Lazarus is to Delphi

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 later in places you might not anticipate them.

Back in September 2006, I said that I thought this tutorial was one of the better ones in my Delphi series. I would still say that, even if it is a bit out of the ordinary. It doesn't chase a particular goal, but some useful techniques arise along the way. I believe that making the effort to go through this will strengthen your programming skills.

Lazarus

In July 2016, I worked through the tutorial, trying to do what it says, but using Lazarus. There were remarkably few things which didn't work exactly as they do in Delphi, and none needed major re-workings to make the operate properly.

This has been my experience generally of "converting" things to Lazarus from Delphi. Even where the text below is clearly speaking of what you do if programming with Delphi, assume it is the same if programming with Lazarus unless there are indications to the contrary.


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 Delphi version of 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.

You can download a .zip file with the sourcecode. The zip also has an .exe file in it, which, as with the Windows version of the program can be run directly from the .zip... although it takes a few seconds to appear, because of all that has to happen. (Nothing is installed on your machine, but it takes a bit of time to extract a scratch copy of the code from the .zip) Everything in the .zip comes from a session on a Win7 machine running the 32-bit Lazarus, version 1.0, of 28Aug2012.

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. Under Delphi, I called this application DD68 (68th Delphi Demo). Name the form DD68f1.

For Lazarus, call the application LD68. (Lazarus version of delphi Demo number 68.) Name the form LD68f1.

Save what you have already. Under Lazarus, create a folder called LD68 where ever you want it, save the .lpi file as LD68 inside that folder. Save the .pas file as LD68u1.pas. If you want to eschew upper case letters, I don't think it will matter... so long as you do so consistently. (And at some levels, Lazarus will just ignore you, force all lower case, anyway! Or maybe mine is doing that because of some system setting I accepted a while ago. I half remember something about Lazarus not being happy with mixed case in file names because in Linux, MyFile.txt and myfile.txt are two different files, and so doing everything in lower case is a Good Practice. Even though I, and maybe you, use Lazarus under Windows, it is a multi-platform language, and has to worry about things that people who only use Windows don't have to. (Don't have to worry about.))

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 nothing "clever" in this that should not work in later versions of the IDE.) (This paragraph needed no edits to be "right" for Lazarus v1.0, 32 bit, on a Win7 machine, 12 July 16.)

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.

Re-save your work.

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." of DD68u1.pas/ LD68u1.pas, add....

procedure TDD68f1.CheckAndHandleAlarmsOrWarnings;
begin
if scrollbar1.position>60 then begin
       scrollbar1.position:=0;
       showmessage('Alarm!!');
       end;
end;

... save, and try to run what you have so far.

If you are using Lazarus, and you get "ld68u1.pas(40,19) Error: Identifier not found "TDD68f1" ", don't be alarmed... I deliberately set you up to fail!

If you are using this tutorial with Lazarus, there will be many, many places where you have to make a certain tweak to things you will see here.

If, here, it says...

Add...
procedure TDD68f1.CheckAndHandleAlarmsOrWarnings;
begin
if scrollbar1.position...

... to your code

... then you will need, every time, to change the TDD68 to TLD68.

Make that change to the start you have made, and now you should find that it "works"....

You should (eventually... the first compile sometimes takes a little while... 40 seconds, not 20 minutes!)...

You should, as I was saying, get a window with a "working" scroll bar in it.

Not much happens when you move the scrollbar... But if you CAN move the scrollbar thumb thing more than 60% of the way 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 also get a reaction to moving the thumb above the 30% position. 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.

Note also that we are setting the scrollbar position back to zero whenever there is an alarm or warning. This is somewhat artificial, but it saves you getting multiple alarms and warnings after changing the scrollbar's position property.

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 it 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.

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

Lazarus: Click File | New Form ... twice. Re-save the project, calling the new units DD68uWarnings and DD68uAlarms.

You will probably want to move things about a bit, to make them easy to get to in the clutter of windows on your screen. In Lazarus, if you can't see one of the forms, go to the source editor, click on the form's tab, press F12. (Something close to that works in Delphi, too.)

At this stage, under Lazarus, I had a bit of a struggle to move the Object Inspector between the different forms. If I went to the Source Editor, selected the form I wanted to work with, and then pressed F12, to bring up the display of the GUI object we were building, then the Object Inspector switched to that object.

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.

You will need StdCtrls in the Uses clause of each form. If the system doesn't add it automatically, do it by hand. (My thanks, and your thanks, to the Gentle Reader who kindly wrote in to point that out.) (I'd be interested: Was it added automatically when you were doing the exercise? Delphi or Lazarus? Version? OS?)

(July 16: Win7, Laz 1.0: The StdCtrls reference was added to the Uses clauses when I put the memos on the forms.)

Check that the "visible" property of the two new forms is set "true", using the Object Inspector (Open with F11. Lazarus: Be sure you are looking at the objects/ properties of the form you mean to be... remember that getting the Object Inspector to switch its attention to the right form isn't (to me, anyway) entirely intuitive.)

We want now to put something in the FormCreate handler of each of the new forms.

If an "empty" FormCreate doesn't exist on one or the other, just double-click on the form to provide yourself with one.

Add to it...

memo1.clear;

Just for the sake of going a step at a time, in DD68u1/ LD68u1, 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....

Delphi: "Form DD68f1 references DD68fWarnings" ((Ummm.. or is it DD68uWarnings?? One or the other! Check carefully what you see on screen. I noticed this possible error when writing the Lazarus version.))

Lazarus: ld68u1.pas(48,21) Error: Identifier not found "LD68fWarnings"

Delphi asks...

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

Say yes.

For Lazarus, you go to the Uses clause of LD68u1, and add LD68uWarnings to the list. (By hand)

N.B.: The warning speaks of not being able to find LD68fWarnings, but you add LD68uWarnings to the Uses list. This is because LD68fWarnings is created within LD68uWarnings.

By the way... when I tried to do all of the above, when I got to the part just covered, I found that somehow the two units with memos had somehow been saved as DD68... instead of LD68. A quick "Save As" for each of them was all it took to set things right. Did you find they were DD68... too? Maybe I just didn't follow my own instructions closely enough.

What have we done by that?...

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....

DELPHI:

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.

LAZARUS: (The only difference is the DDs have been changed to LDs)

procedure TLD68f1.CheckAndHandleAlarmsOrWarnings;
begin
if scrollbar1.position>60 then begin
       scrollbar1.position:=0;
       showmessage('Alarm!!');
       end;
if scrollbar1.position>30 then ReactToWarning;

end;

procedure TLD68f1.ReactToWarning;
begin
       scrollbar1.position:=0;
       LD68fWarnings.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".

Getting fancier....

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');

(Don't try to run the program just yet.... you need also to...)

Change the declaration (Up at the top, near "private" 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. It might have been better to put it where it was before, in the "if" clause in CheckAndHandleAlarmsOrWarnings.

(The "DateTimeToStr(Now)" item is all taken care of for you by Good Old Delphi/ Lazarus. 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. Go back, re-read what you've done. Be sure you are comfortable with what we've done so far before going on.

Happy? Good!

===
We have the application "doing things" nicely when there is a WARNING. (Scrollbar position over 30). Now we are going to make corresponding changes to the code to make ALARMS behave in a similar manner...

Add DD68uAlarms to DD68u1's Uses clause. (LD68uAlarms to LD68u1's Uses clause.)

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: '+inttostr(scrollbar1.position));
if scrollbar1.position>30 then
     ReactToWarning(''Scrollbar Position: '+inttostr(scrollbar1.position)');
end;

Save. Run. Debug.

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?

In DD68u1, in the line after "{Private declarations}", add...

iNumWarnings,iNumAlarms:integer;

Double-click on DD68u1, to start a "shell" FormCreate. Add to it, making it...

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...

       scrollbar1.position:=0;
       inc(iNumAlarms);
       DD68fAlarms.memo1.lines.add(inttostr(iNumAlarms)+
                ': Alarm seen at '+
                DateTimeToStr(now)+
                ' '+sMsg);

=====
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);

Just before the "end." at the bottom of DD68u1.pas, add...

procedure TDD68f1.WriteToMemo(var meWhichMemo:TMemo;sMsg:string);
begin
meWhichMemo.lines.add(sMsg);
end;

... 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, take out the "WriteToMemo...", combine the two lines which write things to the memo, make ReactToAlarm just....

       scrollbar1.position:=0;
       inc(iNumAlarms);
       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.

Doing things differently for different calls...

We won't take it very far here, but the following illustrates an important technique.

Suppose you wanted to record all the warnings and alarms in two data files. One for warnings. One for alarms.

It isn't terribly hard, actually, but there are some details I can't face going into here.

You'd have to add code to set things up to write to the two data files. Part of the "setup" would mean that "writeln(dfWarnings(sMsg))" would write what was in sMsg to the "Warnings" datafile, and "writeln(dfAlarms(sMsg))" would write what was in sMsg to the "Alarms" datafile.

**IF** you had everything set up ahead of the first call to "WriteToMemo", then adding just the following two lines to WriteToMemo would write lines to the two data files...

if meWhichMemo=DD68fWarnings.memo1
         then writeln(dfWarnings(sMsg));
if meWhichMemo=DD68fAlarms.memo1
         then writeln(dfWarnings(sMsg));

Simples!

(Another, less suited to these exact circumstances, approach is to just save the whole memo's contents from time to time. Works well in other circumstances.)

It might be best to rename "WriteToMemo" something like "WriteToMemoAndDatafile" if the extra feature was added!

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 build an "if" statement around a variable 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.

Taking it further

I won't drag you though the details, but you could further "boil down" what we have at this point, and replace ReactToWarning and ReactToAlarm with a single procedure, "React". It would have another parameter which would be passed when the procedure was called. I'd use a byte type variable, because anything "smaller" saves very little, and using a byte-type variable leaves room for future enhancements.

When calling "React", you would set that extra parameter to 0 or 1, and inside "React" you would have some "if (it)=0 then... " and "if (it)=1 then..." statements, so that how the program reacts depends on whether it was an alarm or a warning that triggered the need to react.

Don't "worry" if anything from "Taking it further" wasn't clear... I only set the ideas out very roughly. Grasping that stuff isn't important. A useful (I hope!) extra, if you did grasp what I was trying to say.

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;

At the top of ReactToWarning, add....

boWarningAckd:=false;

Yes: false. (You have not yet acknowledged the warning, have you?)

And add similar to ReactToAlarm.

Make the handler for the buWarningAckd clicked event....

boWarningAckd:=true;
MakeNormal(DD68fWarnings.memo1);

... and do similar for buAlarmAckd.

(It won't compile or run at this stage... you haven't told it how to do the MakeNormal procedure. But apart from two complaints of ...

dd68u1.pas(61,13) Error: Identifier not found "MakeNormal"

... it should almost run!

Just below the "end;" of CheckAndHandleAlarmsOrWarnings add....

if boWarningAckd=false then Flash(DD68FWarnings.memo1,clBlue,clYellow);
if boAlarmAckd=false then Flash(DD68FAlarms.memo1,clRed,clWindow);

... and it should all Just Work.... when you have added the declarations and implementation code for Flash and MakeNormal. I'll leave you to take care of making the declarations. Here's the code for the implementation....

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? but... we're done... once you clear any bugs.

When a warning or alarm arises now, the corresponding memo will still get the new line, recording when the event arose, but now, in addition, the memo will flash colors, to draw attention to itself... until you acknowledge the warning or alarm by clicking the relevant button.

A program you will need in exactly this form? Of course not. A useful illustration of some interesting/ useful programming techniques? I hope so!


            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?"
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 to 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 .....