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

Delphi: Selecting cells, using "Sender", Etc

This page is information rich, and a has 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.
Pick a cell, any cell. Ostensibly, this tutorial is about using code to select a cell of a string grid at run time. Along the way, some more generally important material arises concerning using references to Delphi created objects, e.g. the object in "sender" arising from Delphi created event handlers.
Program driven cell selection, AND direct references to objects within code....

This follows on from an earlier tutorial. You really ought at least to skim Part One before reading on.

The source code for the program, as at the end of Part One, can be downloaded by clicking on either of the following links. You do not need to do both. I'd suggest fetching and running the self extracting archive, but see "Why two options" if you're not sure what would be best.

Click here to download zip archive...Problem? Please report, quote:T45z
Click here to download self extracting exe...Problem? Please report, quote:T45s
      Why two options?

A little farther down the page, you will find a replacement for the sgClick handler. It was extracted (and modified) from the code in MGA15, my commercial elaboration of the ideas in DD45. (You can get that shareware program (not its source code, sorry!, from my freeware/ shareware store).

Once the new sgClick is in place, not only can you do everything you could do before, but the selected cell in the column you DIDN'T click in, to identify a pair, will change to the next non-blank cell. (When you succeed in finding a pair.) That is simple enough to describe. You'd be surprised at how long you could spend trying to achieve that behavior. I count myself lucky: when I started the programming, it went fairly smoothly.... probably merely by luck, as I say. Look CLOSELY at the following code to see how all the pieces are fitting together to achieve the result. (Or, if you like a challenge, try to achieve the result before you look at my code!) (More on this after the code....)

Before the revised code will run, you must also add...
bLastFilled:byte;
...just after...
public
    { Public declarations }
... and you must add...
bLastFilled:=5;
...just after "sgLeft.cells[0,5]:='no';sgRight.cells[0,5]:='fromage';"

The new sgClick handler is as follows.....
procedure TDD45f1.sgClick(Sender: TObject);
var grTmp:TGridRect;
    objOther:TSTringGrid;
    bCurrent,c1:byte;
    boDone:boolean;
begin
grTmp:=sgRight.selection;
  liSGRRight:=grTmp.Top;
grTmp:=sgLeft.selection;
  liSGRLeft:=grTmp.Top;
if sender=sgLeft then
     begin
     objOther:=sgRight;
     bCurrent:=liSGRRight;
     end//no ; here
   else begin
     objOther:=sgLeft;
     bCurrent:=liSGRLeft;
     end;//else
if boPair(liSGRLeft,liSGRRight) then begin
  sgLeft.cells[0,liSGRLeft]:='';
  sgRight.cells[0,liSGRRight]:='';
  //Make sure there is at least one not-blank row
  boDone:=true;
  for c1:=0 to bLastFilled do
       if objOther.cells[0,c1]<>'' then boDone:=false;
  if boDone then
    showmessage('Congratulations on clearing the board!');
  if not boDone then begin
  //Now move the selection in the other
  //   column forward to next not-blank row
  repeat
    inc(bCurrent);
    if bCurrent>bLastFilled then bCurrent:=0;
  until objOther.cells[0,bCurrent]<>'';
  grTmp:=TGridRect(rect(0,bCurrent,0,bCurrent));
  objOther.selection:=grTmp;
  end;//..of "if not boDone..."
  end;
end;
Right.. what's going on? Look at the first line:

grTmp:=sgRight.selection;
grTmp has been declared as a TGridRect object, a type built into Delphi. These objects are rectangles constituted of one or more cells from a string grid. In this code, we are always putting just one cell in any TGridRect object. The code above says "Put into grTmp the cell which is selected in the right hand string grid.

Once we've done that, we can do....

liSGRRight:=grTmp.Top;
That says "Put the y coordinate of that cell into liSGRRight" (SGR for String Grid Row). The values in grTmp.Top will be in terms of the string grid, 0 for the first row, 1 for the second row, 2 for the third (from the top), etc. The numbers are not in pixels, for instance.

So... now we know which row is selected in the right hand string grid. Next....
grTmp:=sgLeft.selection;
  liSGRLeft:=grTmp.Top;
... gets a hold of which row is selected in the left hand string grid.

Watch this next bit carefully! It illustrates an important "trick":
if sender=sgLeft then...
... does something of great power, but something I'm only just beginning to use (at 9/04), so you won't see it illustrated often in my other tutorials (yet). You need to be comfortable with the idea of things being passed to subroutines (procedures and functions) when they are called. The OnClick handler for both string grids, the subroutine we are looking at here, is a subroutine much like any other. However, as an event handler, it is typically called not by some line of code in your program, but by an action by a user while the program is running. None-the-less, it can have... and has had... something passed to it when it was called. That "something" is of type TObject, you can see from the procedure's declaration. And the object which was passed is sitting in the variable (field? Apologies if I'm mixing those terms incorrectly) called "sender". "Sender" isn't some magic, special, Delphi declared word. It is just the name that the compiler "happens" to use when setting up an event handler. So... what's in Sender? It depends which string grid gave rise to the execution of the OnClick handler. Remember: sgLeft and sgRight both use sgClick to react to the mouse being clicked while over the string grid. You've guessed what's in "Sender" by now, haven't you? It is sgLeft or sgRight!! So now we can say....
if sender=sgLeft then
     begin
     objOther:=sgRight;
     bCurrent:=liSGRRight;
     end//no ; here
   else begin
     objOther:=sgLeft;
     bCurrent:=liSGRLeft;
     end;//else
... which puts the other string grid, whichever one wasn't responsible for this call of sgClick, into objOther. bCurrent is given the index of the current position of the selected cell in that string grid, relying (probably unnecessarily, but this is the code I got to work!) on the global variable which we put that information into some time ago.

The next line....
if boPair(liSGRLeft,liSGRRight) then begin
tests whether the user has answered a right answer. There is nothing new about that from the program as we left it at the end of the tutorial which this one follows on from.

Next, as before, we blank out the cells with the pair the user successfully identified....
  sgLeft.cells[0,liSGRLeft]:='';
  sgRight.cells[0,liSGRRight]:='';
Soon, we are going to change which cell is selected in one of the columns. First, though, we are going to make sure that there still is a non-blank cell to move the selection to. As a "by-product" we are also finding out if the user has done, i.e. cleared all the cells. boDone will be true or false accordingly.

In the following, the third line is the heart of the "magic"...
  boDone:=true;//initialize it.
  for c1:=0 to bLastFilled do
       if objOther.cells[0,c1]<>'' then boDone:=false;
... then we do....
  if boDone then
    showmessage('Congratulations on clearing the board!');
... then, if we aren't done, we change which cell is selected. Study the code carefully... it gets a little complicated only because it needs to skip over empty cells, and jump back to the first cell if it gets to the bottom without finding an empty cell. For the moment, don't worry about "grTmp:=TGridRect(rect(0,bCurrent,0,bCurrent));"... treat it as merely putting a reference to the right cell into grTmp. We'll come back to that.
  if not boDone then begin
  //Now move the selection in the other
  //   column forward to next not-blank row
  repeat
    inc(bCurrent);
    if bCurrent>bLastFilled then bCurrent:=0;
  until objOther.cells[0,bCurrent]<>'';
  grTmp:=TGridRect(rect(0,bCurrent,0,bCurrent));
  objOther.selection:=grTmp;
  end;//..of "if not boDone..."
  end;
end;
That line "grTmp:=TGridRect(rect(0,bCurrent,0,bCurrent));" does something cool that lets us accomplish what we want. I hadn't seen it in action before. I found out how about it from the ever helpful Alan Lloyd, who has been posting excellent help at comp.lang.pascal.delphi.misc for YEARS. (You can access the archive with groups.google.com.)
objOther.selection:=
....must be followed by a datum of type TGridRect.

TGridRect is a Delphi-defined type. Here the word is being used as a function. If you put something it can cope with inside the brackets after it, then, in this syntax, TGridRect will return a datum of type TGridRect. While I don't know how, directly, to specify the relevant cell of the string grid, happily, i do know that i can refer to it with rect, a built in function of Delphi.

What we're seeing is called explicit typecasting. (See typecasting (not "type casting"), variable typecasting in the Delphi help file f you want to study this further.)

In case it helps you understand better, let me mention that if you put....
showmessage(inttostr(objOther.width));
...into the code someplace after you've done objOther:=sgRight; or objOther:=sgLeft;, then the showmessage will give you the width of the relevant string grid during the execution of the OnClick handler. Oddly(?) I couldn't get the following to work!...
showmessage(inttostr(sender.width));
I say "oddly", as I thought that all we've done is put what was in sender into objTmp... but I'm obviously missing something here (Yes, I've tried "With sender as TStringGrid..."), my brain aches, I've found a way to do what I want to, so I leave the rest to you!!!

Enjoy.

I'm sure that the use of Sender within the sub-routine is going to be enormously useful when several objects share the same event handler. Did I sufficiently stress the little bit "if sender=sgLeft..."? We named one of the string grids "sgLeft". That "if..." is saying "Was it the control the programmer named 'sgLeft' that gave rise to execution of this handler at this time?"

And so to BED!!!


So! I hope that's shown you some useful ideas. A commercial realization of the ideas in this tutorial is available from....
Sheepdog Software Store

It is called "Matching Pairs, MGA15"




Click here if you're feeling kind! (Promotes my site via "Top100Borland")
            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.


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