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

Delphi: A wind vane/ angle/ compass display. Event handling. The tag property.

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.

I was working on a weather logging program, and I wanted something to show the wind's direction. What I used derives from this. The result is a circle on the screen with an arrow (well, almost) inside it, which can point in any one of 16 directions... the well known North, NE, East, SE, South, SW, West and NW, plus the less well known NNE, ENE, etc which lie between the directions already listed. While the result probably seems somewhat trivial and useless, read on, because the project illustrated some useful general techniques.

Start a new project, call it DD31.
Name the form DD31f1.

Put an image on it. Make it 150 units high, and 150 units wide.

We're going to start with a simple answer, and then develop that into something more clever. Don't despair if the start seems pedestrian.

Add two buttons, named buNorth, buEast, captions 'North' and 'East'.

Provide the following handlers...
procedure TDD31f1.buNorthClick(Sender: TObject);
begin
image1.canvas.moveto(75,150);
image1.canvas.lineto(75,0)
end;

procedure TDD31f1.buEastClick(Sender: TObject);
begin
image1.canvas.moveto(150,75);
image1.canvas.lineto(0,75)
end;
(Throughout, you might want to use "kMax" in place of 150, having put 150 into a variable or constant called kMax early in the program. If you adopt this, then you'd use kMax div 2 where ever you see 75, and 0 where ever you see 0.)

When you run the program, clicking "North" should give a vertical line, clicking "East" should give a horizontal line. Each remains after drawn. Don't be alarmed that you do not see the image until the first line is drawn.

In the "TDD31f1 = class(TForm)..." section near the top of the program, just after....
procedure buEastClick(Sender: TObject);
...add...
procedure buDirClick(Sender: TObject);
and, just before the program's terminal "end.", add...
procedure TDD31f1.buDirClick(Sender:TObject);
begin
if sender=buNorth then begin
  image1.canvas.moveto(75,150);
  image1.canvas.lineto(75,0)
  end;
end;
Run the program, then stop it again. (Delphi may need this for the Object Inspector to become aware of buDirClick)

Bring up a view of the form. Click on buNorth. Go to the "events" section of the Object Inspector, click on the pull down arrow of the "OnClick" event handler line. Set buNorth's OnClick handler to buDirClick.

Delete the two lines of human written code in the original buNorthClick handler, reducing it to....
procedure TDD31f1.buNorthClick(Sender: TObject);
begin
end;
Run the program again. Try buNorth. You should see what you saw before. When you quit the program, and go back to editing it, all traces of buNorthClcik should have been cleared away by Delphi's wonderful RAD environment.

Move the code for handling buEastClick into buDirClick, reset buEast's OnClick handler.

Add....
baX,baY:array[0..15]of byte;
... just after the
  private
    { Private declarations }
...near the start of the program.

Create an FormCreate handler (double click on part of the form) with the following. Be sure to use copy/paste creatively...
image1.canvas.moveto(0,0);
baX[0]:=75;baY[0]:=0;
baX[4]:=150;baY[4]:=75;
baX[8]:=75;baY[8]:=150;
baX[12]:=0;baY[12]:=75;
(The first line should clear the previous annoyance of the image not appearing until the first line is drawn.)

Change buDirClick as follows....
if sender=buNorth then begin
  image1.canvas.moveto(baX[8],baY[8]);
  image1.canvas.lineto(baX[0],baY[0])
  end;
if sender=buEast then begin
  image1.canvas.moveto(baX[12],baY[12]);
  image1.canvas.lineto(baX[4],baY[4])
  end;
Make the first three lines of buDirClick...
procedure TDD31f1.buDirClick(Sender:TObject);
begin
image1.canvas.pen.width:=1;
image1.canvas.ellipse(0,0,150,150);
image1.canvas.pen.width:=4;
This will cause prior lines to be erased before the most recently requested line is drawn. (The two pen.width lines are not essential to the line clearing, but they improve the overall result.)

If you want to get fancy, replace image1.canvas.ellipse(0,0,150,150); with....
with image1.canvas do begin
brush.color:=clBtnFace;
pen.color:=clBtnFace;
rectangle(0,0,150,150);
brush.color:=clRed;
pen.color:=clBlack;
ellipse(0,0,150,150);
moveto(0,0);
end;(*with...*)
That covers MOST of the underlying principles involved in this tutorial. Take a break, have a cup of coffee.

===========
Now we're going to shift up a gear. Most of what is going to be done in the following section could be done simply by extending what we've already seen... but the WAY we're going to do it in the following is much more elegant... if a little less transparent!!

Use the object inspector to set buNorth's "tag" property to zero, and buEast's tag property to 4.

Then change buDirClick. You are going to reduce the whole thing to just...
procedure TDD31f1.buDirClick(Sender:TObject);
var bTmp:byte;
begin
bTmp:=((sender as tbutton).tag);
image1.canvas.pen.width:=1;
image1.canvas.ellipse(0,0,150,150);
image1.canvas.pen.width:=4;

image1.canvas.moveto(baX[bTmp+8],baY[bTmp+8]);
image1.canvas.lineto(baX[bTmp],baY[bTmp])
end;(*buDirClick*)
... and we're going to extend this program to be able to show 16 directions, with very little extra code in buDirClick!! I said we could be elegant!

Change the labels of buNorth and buEast to merely 'N' and 'E' respectively. Move them to the appropriate places on the edge of image1.
Add a button named buNorthEast
Caption it 'NE'
Set it's tag to 2
Set its OnClick handler to buDirClick

To FormCreate, add
baX[2]:=127;bay[2]:=27;
baX[10]:=27;bay[10]:=127;
Now for "arrowheads"... of a sort...

In the declarations just after "private" revise the existing line, to make it....
baX,baY,baX2,baY2:array[0..15]of byte;
.. and to FormCreate, add
baX2[0]:=75;baY2[0]:=10;
baX2[2]:=122;baY2[2]:=32;
baX2[4]:=140;baY2[4]:=75;
...and make buDirClick...
procedure TDD31f1.buDirClick(Sender:TObject);
var bTmp:byte;
begin
bTmp:=((sender as tbutton).tag);
image1.canvas.pen.width:=1;
image1.canvas.ellipse(0,0,150,150);
image1.canvas.pen.width:=4;

image1.canvas.moveto(baX[bTmp+8],baY[bTmp+8]);
image1.canvas.lineto(baX[bTmp],baY[bTmp]);
image1.canvas.pen.width:=12;
image1.canvas.lineto(baX2[bTmp],baY2[bTmp]);
end;(*buDirClick*)
(which only means adding 2 lines, if you did the first "pen" lines earlier)

Now... we have a "Draw line to North" button. A "Draw line to South button" shouldn't be so tricky....

Add the button
Set the tag to 8
Set its OnClick handler to buDirClick

Add
baX2[8]:=75;baY2[8]:=140;
Alter buDirClick as follows. Much is unchanged, but I've quoted it all to avoid problems...
procedure TDD31f1.buDirClick(Sender:TObject);
var bTmp:byte;
begin
bTmp:=((sender as tbutton).tag);
image1.canvas.pen.width:=1;
image1.canvas.ellipse(0,0,150,150);
image1.canvas.pen.width:=4;

if bTmp<8 then begin
  image1.canvas.moveto(baX[bTmp+8],baY[bTmp+8]);
  image1.canvas.lineto(baX[bTmp],baY[bTmp]);
  image1.canvas.pen.width:=12;
  image1.canvas.lineto(baX2[bTmp],baY2[bTmp]);
  end (*no ; here*)
 else begin
  image1.canvas.moveto(baX[bTmp-8],baY[bTmp-8]);
  image1.canvas.lineto(baX[bTmp],baY[bTmp]);
  image1.canvas.pen.width:=12;
  image1.canvas.lineto(baX2[bTmp],baY2[bTmp]);
 end;(*of else*)
end;(*buDirClick*)
We're really doing little beyond what we were doing before the coffee break... it just looks hard because elements have become abstract.

There's just one more thing to deal with. Until now, we have established the coordinates for points on the edge of the circle by hand. The following contains no profound insights into the operation of Delphi, and you can just copy it without close study if you wish. It sets up all the coordinates you need for doing arrows in 16 directions. Add the appropriate buttons to your form, assign all of their OnClicks to buDirClick, set their tag values (0-15, clockwise. N:0, E:4, S:8, W:12)... and you're done!

Add in form's "private" section...
kArrowLength,kPenFudge,kEighthFudge,
bTmpX,bTmpY:byte;
kPenFudge is used to slightly shorten the lines, so that the outside edge of the line is still inside the circle drawn.

kEighthFudge is used to "twist" the arrows slightly on the arrows to directions 1,3,5, etc, i.e. NNE, ENE, ESE, etc.

Replace earlier by-hand baX[], baY[] determinations with...
kWidthHeight:=150;(*This and next: to make
                  doing other sizes easy*)
kRadius:=kWidthHeight div 2;
kCentX:=kRadius;kCentY:=kRadius;
kArrowLength:=10;

(*Constants for N,E,S,W*)
baX[0]:=kRadius;baY[0]:=0;
baX2[0]:=baX[0];baY2[0]:=baY[0]+kArrowLength;
baX[4]:=kWidthHeight;baY[4]:=kRadius;
baX2[4]:=baX[4]-kArrowLength;baY2[4]:=baY[4];
baX[8]:=kRadius;baY[8]:=kWidthHeight;
baX2[8]:=baX[8];baY2[8]:=baY[8]-kArrowLength;
baX[12]:=0;baY[12]:=kRadius;
baX2[12]:=baX[12]+kArrowLength;baY2[12]:=baY[12];

(*Constants for NE,SE,SW,NW*)
kPenFudge:=4;
(*If you are using a "fat" pen for the "arrow" part
  of the line, "ink" spills outside of the circle.
  Adjust the size of kPenFudge to overcome this.
  (Increase kPenFudge if you are experiencing
  overspill after changing the arrow width.)*)
bTmpX:=trunc(cos(pi/4)*kRadius)-kPenFudge;
bTmpY:=trunc(sin(pi/4)*kRadius)-kPenFudge;
(*Yes, we'll need two variables... later*)

baX[2]:=kCentX+bTmpX;baY[2]:=kCentY-bTmpY;
baX2[2]:=baX[2]-kArrowLength;baY2[2]:=baY[2]+kArrowLength;
baX[10]:=kCentX-bTmpX;baY[10]:=kCentY+bTmpY;
baX2[10]:=baX[10]+kArrowLength;baY2[10]:=baY[10]-kArrowLength;

baX[6]:=kCentX+bTmpX;baY[6]:=kCentY+bTmpY;
baX2[6]:=baX[6]-kArrowLength;baY2[6]:=baY[6]-kArrowLength;
baX[14]:=kCentX-bTmpX;baY[14]:=kCentY-bTmpY;
baX2[14]:=baX[14]+kArrowLength;baY2[14]:=baY[14]+kArrowLength;

(*Constants for NNE,ENE,ESE,SSE,SSW,WSW,WNWnNNW*)
kPenFudge:=4;
(*If you are using a "fat" pen for the "arrow" part
  of the line, "ink" spills outside of the circle.
  Adjust the size of kPenFudge to overcome this.
  (Increase kPenFudge if you are experiencing
  overspill after changing the arrow width.)*)
kEighthFudge:=7;
(*This is used to "twist" the arrowheads slightly
  for directions 1,3,5,7,9,11,13,and 15, i.e.
  NNE,ENE, etc *)
bTmpX:=trunc(cos(pi/8)*kRadius)-kPenFudge;
bTmpY:=trunc(sin(pi/8)*kRadius)-kPenFudge;
{showmessage(inttostr(bTmpX)+' '+inttostr(bTmpY));}

baX[1]:=kCentX+bTmpY;baY[1]:=kCentY-bTmpX;
baX2[1]:=baX[1]-kArrowLength+kEighthFudge;
baY2[1]:=baY[1]+kArrowLength;
baX[9]:=kCentX-bTmpY;baY[9]:=kCentY+bTmpX;
baX2[9]:=baX[9]+kArrowLength-kEighthFudge;
baY2[9]:=baY[9]-kArrowLength;

baX[7]:=kCentX+bTmpY;baY[7]:=kCentY+bTmpX;
baX2[7]:=baX[7]-kArrowLength+kEighthFudge;
baY2[7]:=baY[7]-kArrowLength;
baX[15]:=kCentX-bTmpY;baY[15]:=kCentY-bTmpX;
baX2[15]:=baX[15]+kArrowLength-kEighthFudge;
baY2[15]:=baY[15]+kArrowLength;

baX[3]:=kCentX+bTmpX;baY[3]:=kCentY-bTmpY;
baX2[3]:=baX[3]-kArrowLength;
baY2[3]:=baY[3]+kArrowLength-kEighthFudge;
baX[11]:=kCentX-bTmpX;baY[11]:=kCentY+bTmpY;
baX2[11]:=baX[11]+kArrowLength;
baY2[11]:=baY[11]-kArrowLength+kEighthFudge;

baX[5]:=kCentX+bTmpX;baY[5]:=kCentY+bTmpY;
baX2[5]:=baX[5]-kArrowLength;
baY2[5]:=baY[5]-kArrowLength+kEighthFudge;
baX[13]:=kCentX-bTmpX;baY[13]:=kCentY-bTmpY;
baX2[13]:=baX[13]+kArrowLength;
baY2[13]:=baY[13]+kArrowLength-kEighthFudge;
That's it! The program is done! (There are some lovely symmetries in the block of code just presented. Not "important", but you might enjoy them, if you enjoy mathematical beauties.)

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

Info on how to contact this page's editor, 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 .....