                        - October '95 Issue -

Delphi-Talk: majordomo@bridge.net the very best of delphi-talk.
Summary October 1995
Last updated: 13. October 1995 1115 cet

These questions and answers are taken from the delphi-talk of
majordomo@bridge.net and was build up from:
Marko Tietz
tietz@mailserv.rz.fh-merseburg.de

You can read it or download it from:
http://www.fh-merseburg.de/~tietz/delphi.html

It will be regular updated!

I can't guarantee that all answers are correctly! Please use all informations
at your own risk!




Mail me:
- if you find this file useful
- if you want make some suggestions

I'm planning following:
- sort this file by various topics
- distribute it as a windows .hlp-file



=============================================================================

 Contents

  1. Look for an existing Record in database before it will be inserted
  2. Displaying memo field in DBGrid
  3. Alt-? key combination in an aboutbox
  4. Getting the name of a form
  5. Multiselect in a stringgrid
  6. Display certain database fields in columns
  7. Using icons on a SpeedButton
  8. Reorder DBGrid fields programmatically at runtime
  9. Case and Strings
 10. Using ChartFX VBX
 11. Detecting Win95 Vs W3.X
 12. How to determine an existing instance of a program is already running
 13. 32-bit pointer
 14. Create Windows Group file (.GRP)
 15. Multiple tables in one DBGrid
 16. Listing all tables in database
 17. Copy a file to PRN
 18. Bit-wise manipulation
 19. Write / Read array of records into a file
 20. Read data from a file
 21. Array of const
 22. Create 'dynamic' array
 23. BMP rotation
 24. Static Variables
 25. Write a byte to a specific adress
 26. Set a file's date and time
 27. Callback example
 28. Array of controls
 29. Record number of a DBase table
 30. Dynamic Components Solution
 31. Center text in cells of TStringGrid
 32. Intercept WM_KEYDOWN event
 33. Byte swapping
 34. Title in Print Manager
 35. Popup menu by clicking left mouse button
 36. Change the number of tabs in a TTabSet at runtime
 37. Loading a TMemoField into a TMemo component
 38. CheckBox array - using common event
 39. Lasso control

=============================================================================

1. Look for an existing Record in database before it will be inserted
Q:
I'm trying to delete the record inserted or not insert it at all if this
new record has a key field that already exists in the database.

A:
If you are already in the Edit mode or Insert mode, when you change states,
you will automatically attempt to post the record.
And of course, a key violation will cause an error. A way around it is to use
another TTable component mapped to the same table, and do the search on that
one. That way, the table being edited remains unaffected.

A:
Use two TTable components (both pointing to the same table).  Use
one for the search and the other for the editing.

A:
If you "key" the table the BDE will automatically generate an exception
when the user tries to post that create a duplicate key. Use the
Database Desktop to set up the table.

A:
Can you make that field a Primary Index. Then create some DB exception
handling for that key violation.

A:
What I did was prompt the user, with a different form, for the portion of
the record which makes up the unique key (usually just a single entry). I
then did a FindKey to see if it already exists.  If it does, the user is
informed via a MessageDlg, and then returned to the edit form without
creating a new record. Remember that if FindKey fails, the dbCursor is not
moved; no need to bookmark. If it works, then the found record is now
displayed on the edit form and the user can see the contents right away.
Otherwise the following occurs:

        Table.Append;
        Table.FieldByName('KeyField').AsString := UserEntry;
        { ... allow the user to edit all other fields for the record ... }
        { a Cancel button is made active during this time so that if the
          user presses it, the entire new record is canceled. }

In my edit forms, the Unique key field is disabled and shown with a
different color.  This way the user cannot flub up the referential integrity
of the RDB.

-----------------------------------------------------------------------------

2. Displaying memo field in DBGrid
Q:
I'm trying to display the first 100 bytes of a TMemoField in a DBGrid. To do
that, I created a calculated string field and tried the following code:

procedure TfrmDiario.Query1CalcFields(DataSet: TDataset);
var
  s : String;
  p : ^Char;
begin
  with Query1Historico do begin   {Query1Historico is the TMemoField}
    if DataSize>0 then begin
      GetMem(p, DataSize);
      GetData(p);
      StrCopy(PChar(p), @(s[1]));
      s[0] := #100;
      FreeMem(p, DataSize)
    end
    else
      s := '';
    Query1StrHistorico.AsString := s;  {Query1StrHistorico is the calculated
                                        TStringField}
  end;
end;

The problem is that Query1Historico.DataSize is ALWAYS zero! I even
tried Query1Historico.SaveToFile and it indeed created a zero length file.

A:
I also finally loose the hope to see TMemoField.DataSize has another value
than zero. Maybe meaning of the DataSize is size of the part of Memo field
which saved in the .db file. Instead of this I using now the TBlobStream
object which works perfectly. It looks like :

Var
        pBuffer	: PChar ;
        Blob	: TBlobStream ;
begin
        {FDataField is the TMemoField}
        Blob := TBlobStream.Create( FDataField, bmRead ) ;
        try
	if Blob.Size > 0 then
        	try
            	       GetMem( pBuffer, Blob.Size ) ;
	       Blob.Read( pBuffer^, Blob.Size ) ;
{                        do something    }
                     FreeMem( pBuffer, Blob.Size ) ;
            except
                ShowMessage( 'Out of memory' );
            end ;
       finally
    	Blob.Free
      end ;

-----------------------------------------------------------------------------

3. Alt-? key combination in an aboutbox
Q:
I'm trying to obtain the same that Delphi developers done in Delphi About
Box...I'd like to trap the ALT-? key pressing to show hidden information
such revision number etc...how can I do this ?

A:
Anyway here's some code which does similar. However if you have the
shift=[ssalt] condition it means that the keypresses are intepreted by the
default handlers and every keypress generates a beep.
You need to set the previewkey option on the form.

In the key down event:

procedure TAboutBox.FormKeyDown(Sender: TObject; var Key: Word;
  Shift: TShiftState);
var
   i:integer;
   working:integer;
begin
     if (shift=[ssalt]) and (key>=$41) and (key<=$5A) then begin
     s:=s+chr(key);
     working:=0;
     for i:=1 to 4 do begin
         if (s=copy(strings[i],1,length(s))) then working:=-i;
         if (s=strings[i]) then working:=i;
         end;
     if working=0 then s:='';
     if working>0 then showmessage(strings[working]);
     end;
end;

In the form create event: {to ensure that the string is initially empty}

procedure TAboutBox.FormCreate(Sender: TObject);
begin
     s:='';
end;

At the top of the form to define the various messages:
type
    Tst=array[1..4] of string;
const
     strings:Tst= ('HELLO','BYE','VERSION','PROGRAMMER');


In the public section of the form:
  public
        s:string;

A:
Place a button off-screen that has a shortcut of Alt-?. Since it's an about
box, I assume that it's not resizeable and thus the user will never see this
button. Trigger your version and revision info off of the OnClick event of
this button.

-----------------------------------------------------------------------------

4. Getting the name of a form
Q:
I'm trying to get the name of a form at run time.

A:
If you use the ClassName property like this.

   with Sender as TForm do
      Label1.Caption := copy(ClassName,2,length(ClassName)-1);

This will give the desired effect without extra coding in the Form's create
method.

A:
'Sender' may well not be the form in question, and your program will throw an
exception on the invalid typecast. I'm not sure under which conditions (if
any) Sender actually would be the Form itself.

Anyway, you can protect yourself by bracketing the call with a exception
handler or just a simple test, like so:
  If Sender is TForm then
    Label1.Caption := (Sender as TForm).Name ;

If what you're trying to accomplish is the following:
  Label1.Caption := Form1.Name ;

That's a whole different kettle of fish. I've read a lot of complaints
that this or that property of a Form isn't available at run time, most
of the cases seem to be linked to a misapprehension regarding class
initialization. If you read the Delphi docs carefully, you'll note that
setting a property in the Object Inspector does NOT automatically set
that property for run-time purposes.  The answer to THIS situation is to
explicitly set the property (.Name in this case) in the Form's .Create
method. So, some code like the following WILL work:

procedure TForm1.Create( Sender : TObject ) ;
begin
  Form1.Name := 'Form1' ;
end ;

procedure TForm1.Button1Click( Sender : TObject ) ;
begin
  Label1.Caption := Form1.Name ;
end ;

A:
var
  TC: TComponent;
begin
  TC := label1.Owner;
  label1.Caption := TC.ClassName;
end;

A:
I added a button to my form and in its event handler I put:

	name := 'AName';

Then after clicking on the button, I could then click on the form and
the label's caption changed to 'AName'.
The solution is to define the property Name in the form's create event.
I.E. if you have a form named MyForm then in its OnCreate event you
should have:

	name := 'MyForm';

This will solve your problem, but I agree its a little abnoxious.

-----------------------------------------------------------------------------

5. Multiselect in a stringgrid
Q:
I have just started to work with the stringgrid component and want to try
to do a multiselect (as in a listbox). This is where a user can select
multiple (non-adjacent) rows or cells and copy some data from an editbox or
combobox to them.  The help and literature on this are very sparse.
Is this possible or is there a component available which does this?

A:
I did the same thing with a DBGrid. (Not implemented the Shift-MouseDown,
Ctrl-MouseDown staff yet).
For the TStringGrid I think the idea is as following :
1. Then you filling the grid set the Objects[0, ARow] with a some
Boolean object
        like TBooleanObject = class(TObject)
                public
                        Flag    : Boolean ;
                end ;
2. In OnMouseDown and OnKeyDown events change the flag as needed.
3. In OnDrawCell event draw the row according to Objects[0,ARow] flag.

-----------------------------------------------------------------------------

6. Display certain database fields in columns
Q:
Is there a way to only display certain database fields in the columns?
i.e., if I have a table with 20 columns, but, I want to display the 2, 4,
5, 10, 11 and 16th in the grid.

A:
Here is a runtime solution:

Table1.FieldByName(RemovedFieldName).Visible :=  False;

or
Table1.Field[removedFieldNumber-1].Visible := false;

-----------------------------------------------------------------------------

7. Using icons on a SpeedButton
Q:
Probably need a way to extract the application icon into a physical .ICO
file and convert it to a .BMP file.

A:
You can cheat a little and just copyrect the Icon into the Bitmap of a
Speed button.

var
  imgIcon: TIcon;
  imgRect: TRect;
begin
  imgIcon := TIcon.Create;
  imgIcon.Handle := ExtractIcon( 'EXEFILENAME' );

  with SpeedButton1.Glyph do begin
     Width := imgIcon.Width;
    Height := imgIcon.Height;
    imgRect := Rect( 0, 0, Width, Height );
    Canvas.CopyRect( imgRect, imgIcon.Canvas, imgRect );
  end;

-----------------------------------------------------------------------------

8. Reorder DBGrid fields programmatically at runtime
Q:
At design time you can use the fields editor, and at run time you can use
the mouse to reorder database fields in a DBGrid.  How do you do this
programmatically?

A:
uses <fieldname>.index := <fieldnumber wanted>

-----------------------------------------------------------------------------

9. Case and Strings
Q:
I am trying to use a String with the Case statement.

A:
A single Character is considered "ordinal" so, in your case, you could code
the following two level imbeded Case statements:
        Case Code[1] of
             'K':Case Code[2] of
                 'T':begin
                      {code for KTAA to KTZZ}
                     end;
                 'D':begin
                      {code for KDAA to KDZZ}
                     end;
                 END;
              'L':Case Code[2] of
        etc...
As an idea, you could turn the letters into their numeric values with
the ORD and CHR functions, and then do a case statement on them. eg:

function NumConvert(s: String): LongInt;
var
  i: Integer;
begin
   s := UpperCase(s); {work with uppercase strings}
   for i := 1 to Length(s) do
      result := result + (Ord(s[i]) * exp(((Length(s) - i) * 2) * ln(10)));
   {We have taken the ascii value of the placeholder, and multiplied it by
    it's position * 2, eg. an 'A' in the second position (as in cAke) would
    be 65 * 10 ^ ((4 - 2) * 2), which is 650000 etc. The final result should
    be 67657569 for the word 'cake'}
end;

(Note: if you're stuck for an ASCII chart, look in the DOS-QBASIC help file.
       under contents you'll find an ASCII chart, - I'm sure Delphi will
       probably have one somewhere as well. At worst, make a program to
       generate one eg: for i := 1 to 255 do ShowMessage(IntToStr(i)+Chr(i)); )

This function should generate a longint that could be used in a case statement,
for example, if you wanted everything between AA and AC, your case statement
would be:

  s := 'abracadabra';
  l := NumConvert(Copy(s, 1, 2)); {Get the first two characters of the string}
  {at this stage l should contain 6566, check this with delphi debugger...}

  case l of
    6565..6567: ShowMessage('s between AA and AC'); {Includes ACAA to ACZZ}
    6768..7171: ShowMessage('s between CD and GG'); {Includes GGAA to GGZZ}
    7065: ShowMessage('s begins with FA');

-----------------------------------------------------------------------------

10. Using ChartFX VBX
Q:
How do I use the ChartFX VBX?

A:
When you have problems of this nature it pays to edit the file in your
\WINDOWS\SYSTEM directory called VBXNAME.PAS.
It contains a Delphi type declaration for your VBX (created when you
register the VBX with Delphi). At least then you can see it's
properties and methods.

-----------------------------------------------------------------------------

11. Detecting Win95 Vs W3.X
Q:
Does anyone know a quick way of detecting whether or not an application si
running on win 95 or Win3.x?

A:
I don't have Win 95 so I can't say what it will return, but the Windows API
function GetVersion returns the Windows (and DOS) version number (it's in
Delphi help). I'm running Windows for Workgroups under DOS 6.22, and this is
what I get back (Windows 3.1 gives me the same thing, I don't know how to
distinguish between 3.1 and 3.11):

var
   x: longint;
begin
   x := GetVersion;
   integer(x shr 24) --> gives me 6, the DOS major version
   integer((x shr 16) and $ff) --> gives me 22, the DOS minor version
   integer((x shr 8) and $ff)  --> gives me 10, the Windows minor version
   integer(x and $ff) --> gives me 3, the Windows major version

A:
Here's a unit I whipped together the other day in an attempt to do this. It
just checks the WINVER.EXE file for the version of the product it is shipped
with. If there's an "official" way to detect Win 3.x/95, I'd like to know.

unit Testvsn;

interface

uses
  SysUtils, WinTypes, WinProcs, VER;

procedure DoIt;

implementation

procedure DoIt;
var
  VIHandle : LongInt;
  VSize : LongInt;
  VData : Pointer;
  VVers : Pointer;
  Len : Word;
  OutStr : String;

begin
  VSize := GetFileVersionInfoSize('WINVER.EXE', VIHandle);
  If VIHandle = 0
    Then OutStr := 'Windows Version : Unknown L1'
    Else
      Begin
        GetMem(VData, VSize);
        Try
          If not GetFileVersionInfo('WINVER.EXE', VIHandle, VSize, VData)
            Then OutStr := 'Windows Version : Unknown L2'
            Else
              If not VerQueryValue(VData,'\',VVers,Len)
                Then OutStr := 'Windows Version : Unknown L3'
                Else
                  With TVS_FIXEDFILEINFO(VVers^) do
                    OutStr := 'Windows Version : ' +
                      IntToStr((dwProductVersionMS and $FFFF0000) shr 16) +
                      '.' +
                      IntToStr(dwProductVersionMS and $0000FFFF);
        Finally
          FreeMem(VData, VSize);
        End;
      End;
    OutStr := OutStr + chr(0);
    MessageBox(0,@OutStr[1],'Windows Version Test',MB_OK or MB_ICONINFORMATION);
end;

begin
  DoIt;
end.

A:
Uses the GetVersion API to determin the version of windows.

TYPE ver = RECORD
             wmaj : byte;
             wmin : byte;
             dmin : byte;
             dmaj : byte;
           END;
VAR Version : ver;

BEGIN
  Version:=getversion;
  {for windows 95 Version.wmaj = 3
                         Version.wmin = 95;}

-----------------------------------------------------------------------------

12. How to determine an existing instance of a program is already running
Q:
How can a Delphi program check if an existing instance of it is already
running?

A:
If hPrevInstance<>0 then Terminate;

As one of the first lines of the project source will terminate the instance of
the application just run if another is already running.

-----------------------------------------------------------------------------

13. 32-bit pointer
Q:
Does anyone know how to define a 32-bit pointer? If I want to use
LPBitMapInfo, defined in the WinApi as a 32bit pointer to the TBitMapInfo
class, but not reconized by delphi (why?), I will have to create my own
type:

My_LPBitMapInfo =3D ^TBitMapInfo

It should be a 16bit pointer, right?

A:
All pointers in Delphi are 32-bits.  Here is a simple test.
Place the following code into a buttons onclick event handler and you
will get all the verification you need.
ShowMessage( Format('Pointers are %d bytes long or %d-bits',
[sizeof(pointer),8*sizeof(pointer)]) );

A:
I think you're confusing the size of the data element with how it's
interpreted into a physical address.  "Pointers" even on the old
8086/8088 processors were 32 bits in size; including a 16-bit segment
and a 16-bit offset, which were finally combined to create a 20-bit
physical address; some different combinations of segment and offset
could therefore resolve to the same physical address, which led to the
need to "normalize" pointers for some purposes.

For 16-bit Enhanced mode Windows, pointers in all of Borland's Pascal
products have been intra-segment, that is, the "segment" portion of a
given pointer should always resolve to the same extended memory
selector; then, given a 16-bit "offset", this seg/ofs model gives a
maximum of 64kb for a data structure.  Windows provides methods whereby
to overcome this limitation (using the _AHIncr procedure for instance),
but it's not automatic like using C's "Huge" data directive.  16-bit
Delphi doesn't have "Huge" pointers in that sense.  32-bit Delphi is
supposed to fix this.

As far as I can tell, the VCL code that allows reading of greater than
64k bitmaps (for instance) uses (and well conceals) these Windows
kludges; this would be a good resource for seeing how this can be done
in Delphi.

A:
Yes, but they're 16:16 pointers, as opposed to the 16:32 pointers he probably
wanted.

A:
Gentlemen, we are talking apples and oranges here. If the C DLL is compiled
by a 16 bit compiler, the pointers in Delphi are ABSOLUTELY compatible with the
16:16 bit far and huge pointers in C (or C++). There is not problem
whatsoever. Just
pass them.

On the other hand, if the C (or C++)application is a Win32 application, it uses
32 bit offsets only. The segments (more appropiately selectors) are
invisible and pressumed of
zero linear base address.
Note that i said application. Ring 0 components also use 32 bit pointers
but might rarely use 48 bit pointers. VXD's are the ring 0 components.

To call a 16 bit application you just do it. Nothing special. To call or be
called by
a Win32 application you need a thunk. Not just for the pointers but also to
change from
a 16 bit stack to a 32 bit stack. And much more. If this is what you need to
do, check the
file CALL32NT.ZIP, available in many places in the web.

-----------------------------------------------------------------------------

14. Create Windows Group file (.GRP)
Q:
Does anyone has the information on creating a windows group file at their
finger tip?

A:
In the Delphi samples sub directory their is a sample program that creates a
Program Group via DDE.

-----------------------------------------------------------------------------

15. Multiple tables in one DBGrid
Q:
Anyone know of a way to have several fields from multiple tables in one
DBGrid?

A:
The only easy way to do it I know is Calculated fields.

A:
As far as I have been able to determine, the only way to put data from
multiple tables into a DBGrid is to use a TQuery object. The catch is:
TQuery is read-only unless you meet some pretty rigid guidelines, one of
which is the data can only come from one table.

-----------------------------------------------------------------------------

16. Listing all tables in database
Q:
How to list all tables in database?

A:
There's a component called TSession, which Delphi always uses, that
keeps track of all Databases in uses. Using this it is possible to
fill a ListBox or array of all the Tables used by a particular
Database. In fact there is a demo that comes with Delphi that does
eactly that. We simply took this and expanded it to display Field
info and Index names.

-----------------------------------------------------------------------------

17. Copy a file to PRN
Q:
I want to copy a file on disk to PRN: (to the printer). This file is
printerdata for a HP laserjet 4L that has been printed to file by a
word-processor.

A:
It's considered to be very poor Windows programming (because it bypasses
all of Windows device virtualization), but you can open DOS text devices
(like PRN, AUX, COMx, LPTx, even NUL) as if they were text files and
write to them in the same fashion.  Try something like:

program WritePrn ;
var
  F : text ;
begin
  AssignFile( F, 'PRN' ) ;
  ReWrite( F ) ;
  WriteLn( F, 'Hello World!' ) ;
  Close( F ) ;
end .

Note that you'll probably need to qualify 'text' and 'Close()' with
'System.' to resolve the name conflicts with VCL.  I'm not sure if this
will continue to work under Windows95 and/or with 32-Bit Delphi; but it
does with 16-Bit Delphi under Windows95, I just tried it.

-----------------------------------------------------------------------------

18. Bit-wise manipulation
Q:
Can some one explain how to do bit-wise manipulation?

A:
Ok I will give it a 4 bit example.
Bit masks are usually used for things like properties.

For a 4 bit number the max value is 1+2+4+8 = 15 (or 1111). If one
property is attached to each bit ( 1 = True, 0 = False) the resultant
number is always unique.

Now we wish to find the status of a particular bit. It would be
fairly tedious to convert to binary or write an algorithm to do this
in decimal. Lets assume we we have a number say 7 (0111) and  want the
setting of the third bit (from the right). The value we wish to check
for is 0100 (4 in decimal).

So we are comparing two values    0100 and 0111    literally bit by
bit. Here are some sample results which should show how to use
bitwise operators

0100               0111

4          AND       7          =  0100   (4)
                             (=4 so the bit we want is set to 1)
4           OR          7          = 0111   (7)
                             (not much use in this example)
4           XOR       7          =  0011  (3)

If you are following the thread I think you are AND is what you are
looking for. As you know exactly what you are checking for this is
the only bit that you set in your comparison value which means that
the only possible result is 0 or (in this example) 4

So your code would be
If  (YourCheckVar AND 4) = 4 Then
     3rd bit is set
Else
     3rd bit is not set;

-----------------------------------------------------------------------------

19. Write / Read array of records into a file
Q:
I need to save a array of record variable. Which is the best way to do it?
It's possible read the file with data-aware component?

A:
It's not very Delphi-like (but then, neither are Pascal records really),
but you can read and write records to and from a file using the standard
Pascal file manipulation procedures like so:

type
  TMyRec = record ;
    Field1 : integer ;
    Field2 : string ;
  end ;

  TMyRecArray = array [0..9] of TMyRec ;

var
  MyArray : TMyRecArray ;
  MyRec : TMyRec ;
  RecFile : file of TMyRec ;

begin

  {...some code that intializes MyArray goes here...}

  AssignFile( RecFile, 'MYREC.FIL' ) ;
  ReWrite( RecFile ) ;
  for i := 0 to 9 do
  begin
    Write( RecFile, MyRec[i] ) ;
  end ;
  CloseFile( RecFile ) ;

You can also use Read() to get a record from such a file, and Seek() to
move to a particular record in the file (starting with 0).  For more
details on these have a look at "I/O Routines" in the Delphi on-line
help.

If you want to do this with the Data Aware components, you'll have to
construct a database where the database "records" reflect the structure
of your Pascal records, then provide translation routines to get the
data from one to the other.  I'm not aware of any way to do this
directly, but it could certainly be encapsulated in a component.

-----------------------------------------------------------------------------

20. Read data from a file
Q:
How can I read data from a file being created by another program?
We have a program that collects data and constantly appends to a
daily file (ASCII text file). Even though the file has data in it, the file's
size is 0 until the program that is writing to it closes it. I need to read
records from this file as they are appended. I try reading from the file with
a Delphi program (see code below), it refuses to read any data from the file
until the other process closes it and the file size is updated.

procedure TestRead(FileName: String);
var
    AMAFile: File;
    BlockBuffer: Pointer;
    Result: Integer;
 begin
    BlockBuffer :=3D AllocMem(BlockSize);
    AssignFile(AMAFile, FileName);
    FileMode :=3D 0;
    try
       Reset(AMAFile, BlockSize);
    except
       MessageDlg('Unable to access ' + FileName, mtError, [mbOK], 0);
    end;
    BlockRead(AMAFile, BlockBuffer^, 1, Result);
    if(Result < 1) then
       MessageDlg('Unable to read first record from ' + FileName, mtError,
                   [mbOK], 0)
    else
       MessageDlg('Successfully read first record from ' + FileName,
                   mtInformation, [mbOK], 0);
    CloseFile(AMAFile);
    FreeMem(BlockBuffer, BlockSize);
 end;

A:
There is a procedure Flush that works with open text files.

     flush(f);

The manual is not clear on whether Flush commits the file to disk. If it
doesn't there would be data in other temporary buffers. As an extra measure
of safety I normally follow it with a call to dos. This call may not be needed
but, just in case.

A possible example follows:

Uses
     Sysutils;
var
     F    : text;             { this is your text file }
Procedure TextFlush(F : Text);
var
     fhandle   : word;
begin
   Flush(F);
   fhandle := ttextrec(F).Handle;       { get the msdos handle }
     asm
          mov  ax, $6800
          mov  bx, handle
          call DOS3CALL
          end;
   end;

If the file is a block file skip the flush step and use tfilerec instead of
ttextrec.

A:
The Filemode variable determins how the file is opened (By default in
exclusive mode).
Unfortunately it doesn't work on text files so you'ld have to use blockreads
and writes into a buffer and then convert the sections of the buffer to
strings if you want to handle it as a text file.

A:
Assign or AssignFile as it is now known cannot be used on a file that
is already open (I checked and this is documented). Now for one of my
famed shots in the dark - Why not use the API call to OpenFile
that's probably what you are using in C anyway.

A:
If it's a text file you first flush the text buffer with flush

     flush(f)

The rest applies to all files:
Commit the file using the dos commit function, available
since DOS 5.

     asm
          mov  ax, $6800                { commit file }
          mov  bx, ttextrec(f).handle   { get the handle of the file }
          call dos3call                 { this is the preferred way,  INT
$21 would work as well }
          end;


According to Microsoft documentation, this call also flushes the SMARTDRIVE
buffers.
The applicable MS language is as follows (MSDN10):

To flush the data held by the SMARTDRV.EXE version 4.0 buffer, you can
do one of the following:
 - Use the MS-DOS Commit File function (which writes changed data from
   the buffer). This is Interrupt 21h, function 68h.
 - Use the MS-DOS Disk Reset function (which writes changed data and
   invalidates/empties the cache). This is Interrupt 21h, function
   0Dh.

-----------------------------------------------------------------------------

21. Array of const
Q:
InsertRecord() takes an array of const.  How do I build an array of const
at runtime?

A:
procedure foo(a : array of const);
implementation
 var
     var1      : longint;
     var2      : pointer;
     var3      : integer;
begin
     var1 := 12345678;
     var2 := @var1;
     var3 := 1234;
     foo([var1, var2, var3]);

Actually an array of const is more correctly called array of tvariant.
Tvariant is a multiple choice kind of variable that can take an number of
types. It has its heritage in Visual Basic. Delphi allows to use either name.

A:
Define a type such as:

TYPE
  NAME1 = Array[1..4,1..10] of Integer;

Then, in your CONST section:

NAME2 : NAME1 = ((1,2,3,4,5,6,7,8,9,10),
                 (1,2,3,4,5,6,7,8,9,10),
                 (1,2,3,4,5,6,7,8,9,10),
                 (1,2,3,4,5,6,7,8,9,10));

----------------------------------------------------------------------------

22. Create 'dynamic' array
Q:
How do I create a 'dynamic' array?  I need to vary the number of elements
in the array at run-time.

A:
Assuming you want to store "GIZMOS" in your array, try the following:
CONST
  MaxGIZMOS = $FFFF Div (SizeOf(GIZMOS)) { Or whatever the maximum number of
GIZMOS is going to be...}

TYPE
  pGIZMOArray = ^GIZMOArray;
  GIZMOArray  = Array[1..MaxGIZMOS] of GIZMOS;

VAR
  TheGIZMOS: pGIZMOArray;
  GIZMOcount: integer;
BEGIN
  GetMem(TheGIZMOS,(GIZMOcount+1)*SizeOf(GIZMO)); {Need 1 extra as GetMem
array is zero-based...}
  TheGIZMOS^[index] := Whatever;
etc...

A:
TList is such a dynamic array. Look for details in the Help section. If you
want to do it yourself you have to use GetMem to allocate a pointer to dynamic
memory and later FreeMem to release the space of the dynamic array. Tlist does
all that for you in a painless way.

-----------------------------------------------------------------------------

23. BMP rotation
Q:
I want to rotate BMPs (rotate to any arbitrary angle around its center of mass)
on form with Delphi code.

A:
I can think of a brute force method, but its efficiency might be
questionable, don't try it without a co-processor!

Do a pixel-by-pixel mapping from a source bitmap to a target bitmap (using
the Canvas.Pixels property). For each pixel do a rectangular-polar
coordinate conversion, add the angle offset to the polar coordinate, then
convert back to rectangular and place the pixel at these new coordinates in
the target bitmap. You may also have to dither in a few missing pixels
depending on the accuracy / rounding of your arithmatic.

The X, Y transformation you would need would be as follows:


X,Y    = old pixel coordinates
X1,Y1  = new pixel coordinates
T      = rotation angle (in radians)

R, A   - intermediate values representing the polar coordinates

R = Sqrt(Sqr(X) + Sqr(Y));

A = Arctan(Y/X);

X1 = R * Cos(A+T);

Y1 = R * Sin(A+T);


I really hope there is a better way of doing it, but if you can't find
anything else, this may be worth a try. I'm happy it will work, but may be
rather slow.

A:
>Do a pixel-by-pixel mapping from a source bitmap to a target bitmap (using
>the Canvas.Pixels property).

This is a good start -- but try thinking of it the other way around.  Do a
pixel-by-pixel mapping from the target bitmap to the source bitmap, so
you're thinking about where the pixels are coming from, rather than where
they are going.

Here is a formula for rotation about the origin:

x, y = coordinates in the target bitmap
t = angle
u, v = coordinates in the source bitmap

x = u * cos(t) - v * sin(t)
y = v * cos(t) + u * sin(t)

Now, if I've solved these equations right for u and v, here's what they
would be (no guarantees, that's why I've included the originals!):

      x * cos(t) + y
u = --------------------
    sqr(cos(t)) + sin(t)

v =   y * cos(t) - x
    --------------------
    sqr(cos(t)) + sin(t)

So, assuming you know the angle of rotation, you might calculate the
constants cos(t) and 1/sqr(cos(t))+sin(t) just before your loop; it might
look something like this (some adjustment and clipping needed):

ct := cos(t);
ccst := 1/sqr(cos(t))+sin(t);
for x := 0 to width do
  for y := 0 to height do
    dest.pixels[x,y] := source.pixels[Round((x * ct + y) * ccst),
                                      Round((y * ct - x) * ccst)];

If you wanted to speed it up, and didn't care about accumulated round-off
error, you could realize that since you're moving a pixel at a time, the
distance between pixels in the u,v map is constant as you move across the
row, and again as you move down the column. I'll use the calculated
variables above as shorthand. Just plug in (x,y) = (1,0) and (x,y) = (0,1)
in the equation above to get:

duCol := ct * ccst;
dvCol := -ccst;

duRow := ccst;
dvRow := ct * ccst;

uStart := 0;
vStart := 0;

for x := 0 to width do
  begin
    u := uStart;
    v := vStart;
    for y := 0 to height do
      begin
        dest.pixels[x,y] := source.pixels[Round(u), Round(v)];
        u := u + rowdu;
        v := v + rowdv;
      end;
    uStart := uStart + duCol;
    vStart := vStart + dvCol;
  end;

All code provided as-is and without warranty!

If you're adventurous and want to try rotation around an arbitrary point,
try solving these for u and v:

Xp, Yp (X-sub-p, Y-sub-p) is the pivot point
others defined as above

x = Xp + (u - Xp) * cos(t) - (y - Yp) * sin(t)
y = Yp + (y - Yp) * cos(t) - (x - Xp) * sin(t)

A:
The original equations:

  x = u * cos(t) - v * sin(t)
  y = v * cos(t) + u * sin(t)

are correct but when I solve for u and v, I get this:

      x * cos(t) + y * sin(t)
  u = -----------------------
     sqr(cos(t)) + sqr(sin(t))


      y * cos(t) - x * sin(t)
  v = ------------------------
      sqr(cos(t)) + sqr(sin(t))

-----------------------------------------------------------------------------

24. Static Variables
Q:
Does DELPHI allow static variables? Static, in the C sense, a variable declared
in a procedure that does not disappear when the procedure is exited, and is
available when the procedure is next entered.

A:
Yes, it does. You have declare the variable in the const section,
for example.

procedure P;
const
  MyVariable : Integer = 0;
begin
  Inc(MyVariable);
end;

In this example MyVariable holds the P calls number.

  However, often it is a better to use the field of the object instead
(if possible).

-----------------------------------------------------------------------------

25. Write a byte to a specific adress
Q:
Does anyone know how I can write a byte to a specific memory address
using Delphi?

A:
Delphi (and Borland Pascal in general) have buried in them an array
called  Port[].  All you have to do is address the bytes directly.  This
would write 0 to a port a 300 hex:

   Port[768] := 0;

Obviously you can use hex values too ($300 etc.)

I think there is also another array which I have not used called mem[] that
allows access to memory via segment and offset.  We played with this one to
write pixels to the DOS graphics screen.

A:
Here is the help section on Mem, MemW and MemL, right from Borland:

Object Pascal implements three predefined arrays which are used to directly
access memory.

Mem
	MemW
	MemL

Each component of Mem is a byte, each component of MemW is a Word, and each
component of MemL is a Longint.
The Mem arrays use a special syntax for indexes:

Two expressions of the integer type Word, separated by a colon, are used to
specify the segment base and offset of the memory location to access.
Here are two examples:

Mem[Seg0040:$0049] := 7;     {stores the value 7 in the byte at $0040:$0049}
Data := MemW[Seg(V):Ofs(V)]; {moves the Word value stored in the first 2
bytes of the variable V into the variable Data}

. . .

I just gave you the good news. The bad news is that Windows uses the 386
paging mechanism. This means that an address in software in segment:offset
pointer format is first converted to a 32 bit linear address. This linear
address is then converted to a physical address through the page tables in
the 386. The result is that the physical address in your custom board may or
may not be the same as the one seen by your Delphi program.

A:
The Mem[] function works perfectly fine. Remember that in Windows you
are operating in protected mode. Thus you are dealing with selectors
not segments. If you want to access memory at segment $E000, you have
to first create a selector for it. Once you have a selector, you can
access the segment with the Mem[] function.

Here is the code to obtain a selector for $E000 from DPMI.

  asm
    mov ax, 2
    mov bx, $E000
    int $31
    mov [SegE000],ax
  end;

  Data := mem[SegE000:0];

-----------------------------------------------------------------------------

26. Set a file's date and time
Q:
I have tried to set the date and time of a file I have created. I am
actually using the sample unit FMXUtils which is supplied with the
Delphi disks for the File Manager sample (in the documentation samples).
I found that the code in the unit was actually commented out and that
it did not actually work when I removed the comments. I've tried using
SetFTime, DOS calls (in inline assembly code) and other methods without
luck. No error is returned and the file's date and time are unaltered.
I don't want to have to call a shelled executable routine either.

A:
You've been in the correct direction for trying to use SetFTime. Below is
a few line of source that alter the date & time of a file:

var
  f: file;
begin
  Assign(f, DirInfo.Name);
  Reset(f);
  SetFTime(f, Time);
  Close(f);
end;

-----------------------------------------------------------------------------

27. Callback example
Q:
I have to call a function in my main prg from a DLL, and the only way to do
this seems to be to use callbacks. Does anyone have a source example of how
this can be done in Delphi?

A:
This is how I'm having a C++ DLL callback a Delphi procedure:

In Delphi:
----------
interface

var
  callBackProc : TFarProc;

procedure delphiProc (const x: Pchar); export;
procedure setupDLL (p: pointer);

implementation

procedure setupDLL (p: Pointer);  external 'MYDLL';

procedure delphiProc (const x: Pchar); { this is the procedure called back }
begin
   ...
end;


procedure TForm1.FormCreate(Sender: TObject);
begin
   ...
   callBackProc := makeProcInstance(@delphiProc, gInstance);
   setupDLL (callBackProc);
   ...
end;

procedure TForm1.FormDestroy (Sender: TObject);
begin
   ...
   freeProcInstance (callBackProc);
   ...
end;


In the C++ DLL:
---------------
   static void CALLBACK (*saveProc)(char*);

   void FAR _export pascal setupDLL (void CALLBACK (*func)(char*))
   {
      saveProc = func;
   }

then, to call the Delphi proc from the C++ DLL, use:

  (*saveProc)(msg);  // where msg is a char*

You don't have to do the makeProcInstance or freeProcInstance where I've
shown them, you just need to make sure you do the makeProcInstance and setup
calls before the callback is used, and the freeProcInstance after the last
time the callback is used.

If your DLL is in Delphi, the code would be similar, except I don't remember
how to call a procedure in Delphi via a pointer to that procedure.

If your DLL is not in Delphi, make sure you keep aware of string/pchar
consistency.

A:
The syntax is the same. In the main prg you just add the keyword EXPORT to
each function you need to export, just as if in a .DLL. Near the end of the
.DPR file, just before the BEGIN keyword that defines the start of the main
program you add a section called EXPORTS where you list again the routine(s)
you want to make available for callbacks. The not so obvious but very important
step you must do is to compile the main prg with the compiler smart callbacks
option off. This is very important, as the routines in the main prg must be
able to access their own data.

-----------------------------------------------------------------------------

28. Array of controls
Q:
In VB when I make a Copy and Paste of a control, VB proposes to build an array
of controls. Do you know how to do something similar in Delphi? Warning: I
don't want to share a methode but to access to several controls in a loop.

A:
You can have an array of objects, but the visual designer won't build it for
you automatically. IMO, this is a "good thing", not a limitation. In every
case where I wanted to do this the array was more complex than a single
control (in one real world example it was a dynamic array of up to 26 bevels
that each had an image, a tick box, a label and a gauge within it) and the
layout on the form is typically more complex than a vector layout (in that
example it was a two-dimensional array which depended on the size of the
client area of the window at the moment and had to feedback adjustments to the
window size based on the existent suite of hardware and share devices).

My suggestion is to build the single prototype of the control complex that you
need on the form, but make it invisible and not enabled. Then define a
descendent of TObject that holds all of the various components involved (this
step may be omitted if the component structure has its own top level
hierarchical object like a TPanel). Finally, define an array of that object
and put a variable of this type on the form.

At execution time you can instantiate components to build each array entry
(copying the prototype to preset most properties) and set its attributes for
position. Remember to insert these dynamic components into their owner so that
they'll appear (the owners of the prototype are where to insert the new
controls). It may sound complex, but it is really simple and takes little code.
Yet it offers much more control than the VB array does -- that's the difference
between a real language like ObjectPascal and a RAP tool like VB.

-----------------------------------------------------------------------------

29. Record number of a DBase table
Q:
I am trying to obtain the current record number of a DBase table. I am,
at present, able to get the physical record number ( i.e. The record
number allocated when the record was added to the table ).

A:
If the dataset is based upon a Paradox or dBASE table then the record number
can be determined with a couple of calls to the BDE (as shown below). The
BDE doesn't support record numbering for datasets based upon SQL tables, so
if your server supports record numbering you will need to refer to its
documentation.

The following function takes as its parameter any component derived from
TDataset (i.e. TTable, TQuery, TStoredProc) and returns the current record
number (greater than zero) if it is a Paradox or dBASE table. Otherwise,
the function returns zero.

NOTE: for dBASE tables the record number returned is always the physical
record number. So, if your dataset is a TQuery or you have a range set
on your dataset then the number returned won't necessarily be relative to
the dataset being viewed, rather it will be based on the record's physical
position in the underlying dBASE table.


    uses DbiProcs, DbiTypes, DBConsts;

    function RecordNumber(Dataset: TDataset): Longint;
    var
      CursorProps: CurProps;
      RecordProps: RECProps;
    begin
      { Return 0 if dataset is not Paradox or dBASE }
      Result := 0;

      with Dataset do
      begin
        { Is the dataset active? }
        if State = dsInactive then DBError(SDataSetClosed);

        { We need to make this call to grab the cursor's iSeqNums }
        Check(DbiGetCursorProps(Handle, CursorProps));

        { Synchronize the BDE cursor with the Dataset's cursor }
        UpdateCursorPos;

        { Fill RecordProps with the current record's properties }
        Check(DbiGetRecord(Handle, dbiNOLOCK, nil, @RecordProps));

        { What kind of dataset are we looking at? }
        case CursorProps.iSeqNums of
          0: Result := RecordProps.iPhyRecNum;  { dBASE   }
          1: Result := RecordProps.iSeqNum;     { Paradox }
        end;
      end;
    end;

-----------------------------------------------------------------------------

30. Dynamic Components Solution

A:
Warning to Newbies:
If you simply want to create a component whos type you know at compile time
then you should read the delphi\doc\VB2Delph.wri file to see how this is done,
it also explains control arrays and is just darn good reading anyway. This post
explains how to utilise the RTTI facilities in Delphi.

The first thing that your application needs to do is register all the classes
that you know you are going to need create at some stage in your program. This
can be done using either the RegisterClass(), RegisterClasses() or
RegisterClassAlias() functions.

eg.
    procedure TForm1.FormCreate(Sender: TObject);
    begin
        RegisterClasses([TButton, TEdit, TMemo, TLabel]);
    end;

This may seem like a limitation but then Delphi is a static language. If you
want true dynamic creation of objects in a weakly typed late bound environment
then use a dynamic language like Smalltalk. I have a feeling that Delphi uses
this registration mechanism to register all the components in the DCL when it
starts up, which allows it to create any component at design time.

Creating the components. Use the FindClass() function to return a class
reference to the component that you want to create and call its Create method.
Easy, isn't it? In the example I have typecast SomeComponent to a TControl so
that I can set its parent property (I can do this because I know that all the
classes that I registered were descendants of TControl). You need to set the
parent property of a control to make it appear on the form.

eg.

    procedure TForm1.CreateClick(Sender: TObject);
    begin
        SomeComponent:= TComponentClass(FindClass(ClassName.Text)).Create(Self);
        (SomeComponent as TControl).Parent := Self;
    end;

Now that you have the component, how do you set it's properties without using
the biggest case statement in the universe? Use the GetPropInfo() function to
get the run-time type information (RTTI) structure for the property, and then
use the SetXXXXProp() set of functions to set it's value. (Note: These
functions are not documented in the Delphi help files. OO programming means
reading other peoples code and building on it, not reinventing the proverbial
wheel.) Each SetXXXXProp() function also has an equivalent GetXXXXProp()
function so you can inspect an objects property values.

eg.

    procedure TForm1.SetPropertyClick(Sender: TObject);
    var
        PropType: PTypeInfo;
        PropInfo: PPropInfo;
    begin
        PropInfo := GetPropInfo(SomeComponent.ClassInfo, PropertyName.Text);
        PropType := PropInfo^.PropType;
        case PropType^.Kind of
            tkInteger:
                SetOrdProp(SomeComponent, PropInfo,
StrToInt(PropertyValue.Text));
            tkChar:
                SetOrdProp(SomeComponent, PropInfo, Ord(PropertyValue.Text[1]));
            tkEnumeration:
                SetOrdProp(SomeComponent, PropInfo, GetEnumValue(PropType,
PropertyValue.Text));
            tkFloat:
                SetFloatProp(SomeComponent, PropInfo,
StrToFloat(PropertyValue.Text));
            tkString:
                SetStrProp(SomeComponent, PropInfo, PropertyValue.Text);
        end;
    end;

You can also set the values of Set, Class and Method properties but this can be
a little bit more complicated. I might post how to do that a little bit later.

And that's it. It's pretty amazing what you can find out by reading the VCL
source code.

This is tempting feature, but has potential for mass abuse. There is no
substitute for a good understanding of the other ways of achieving the same
things in Delphi and choosing the technique most appropriate to your design.

-----------------------------------------------------------------------------

31. Center text in cells of TStringGrid
Q:
How justify the text inside the cell of a grid?

A:
Supply your own drawcell method here's an example that we've used.

procedure Tsearchfrm.Grid1DrawCell(Sender: TObject; Col, Row: Longint;
  Rect: TRect; State: TGridDrawState);
var l_oldalign : word;

begin
  if (row=0) or (col<2) then
    grid1.canvas.font.style:=grid1.canvas.font.style+[fsbold]; {set the headings in bold}

  if col<>1 then
   begin
 l_oldalign:=settextalign(grid1.canvas.handle,ta_right);
 {NB use the righthand side of the drawing rectangle}
 grid1.canvas.textrect(rect,rect.right-2, Rect.top+2,grid1.cells[col,row]);
       settextalign(grid1.canvas.handle,l_oldalign);
   end
  else
   begin
     grid1.canvas.textrect(rect,rect.left+2,rect.top+2,grid1.cells[col,row]);
   end;

  grid1.canvas.font.style:=grid1.canvas.font.style-[fsbold];

end;

-----------------------------------------------------------------------------

32. Intercept WM_KEYDOWN event
Q:
I'm just writting a new component base od TCustomControl and I have
problems with intercepting KeyDown event.
I want to intercept VK_DOWN. The overriden KeyDown method is not
called when I press down arrow key (for other keys it's OK). The
same with intercepting WM_KEYDOWN method:
   procedure WMKeyDown(var Message : TMessage); message WM_KEYDOWN;

A:
The VK_DOWN, VK_UP, and the VK_TAB can't be accessed by using KeyDown.  Use
the 'OnMessage' event to handle those keys. The 'OnMessage' event gives you
the full message along with all it's parameters.

A:
What you need to do is override the KeyDown method of TCustomControl ie.
in the protected section of you new object type, insert this line:

    procedure KeyDown(var Key: Word; Shift: TShiftState); override;

then in your object's implementation you do something like:

procedure TMyObject.KeyDown(var Key: Word; Shift: TShiftState);
begin
  inherited KeyDown(Key, Shift);
  if (Key in [VK_UP, VK_DOWN, VK_NEXT, VK_PRIOR, VK_HOME, VK_END]) then
    {do key movement work}
end;

A:
1. To write OnMessage hander for the Application
2. To intercept WM_GETDLGCODE message first and return DGLC_WANTARROWS.

The probles was that messages WM_KEYDOWN: VK_UP, VK_DOWN, VK_TAB are
not sent to the control as long as the control does not response
to WM_GETDLGCODE message.

-----------------------------------------------------------------------------

33. Byte swapping
Q:
Does anyone have the algorithms for swapping bytes between big-endian and
little-endian format.

A:
Check the function swap in Delphi Help. There is a typo in their definition,
the correct one is:

     function Swap(X) : word;

Their example is as follows:

var
  X: Word;
begin
  X := Swap($1234);   { $3412 }
end;

Swap will provide a big-endian when you feed it with a little-endian and
viceversa.

A:
To swap a 16-bit integer or word, use

   value: integer;
   value := swap(value); {builtin Pascal function}

To swap a 32-bit long, use

   value: longint;
   value := swap(value shr 16) or (longint(swap(value and $ffff)) shl 16);

I don't know what the standards are for floating point values, but if they
conform to integer and longint (my guess is they do), then you'd use (for
double):

   value: double;
   block: array[0..7] of byte absolute value;
   temp:  byte;

   for i := 0 to 3 do begin
      temp := block[i];
      block[i] := block[7-i];
      block[7-i] := temp;
   end;

If you're using real, array should be 0..5 and the for loop 0 to 2; if
you're using comp, 0..7 and 0 to 3; for single, 0..3 and 0 to 1; for
extended, 0..9 and 0 to 4.

You can also change this to a procedure that takes a pointer to the value
and the size of the value, eg. (this is off the top of my head, may need
tweaking):

   procedure swapper (valin: pointer; size: integer);
   var
      i: integer;
      temp: byte;
      val:  ^byte;
   begin
      size := size - 1;
      val := valin;
      for i := 0 to (size div 2) do begin
         temp := val[i];
         val[i] := val[size-i];
         val[size-i] := temp;
      end;
   end;

then you can call this using

   swapper (@value, sizeof(value));

(This would also work for longint, integer, etc. but swap() is more efficient.)

Note that in addition to swapping the bytes, you may have to do a format
conversion (from eg. Microsoft floating point to ANSI); I don't dabble in
floating point, so I don't know for sure.  Best thing might be to flip the
bytes and see if the other system recognizes it as the same number that
Delphi does.

A:
I had a similar problem, I was reading a TColor from disk, in RGB
format ($RRGGBB), while delphi uses BGR format.

I solved my problem by and'ing the initial number with a mask, and
retrieving the values, and then placing them into my format. eg:

color := $F03200;
r := (color and $FF0000) div $010000; {should return $F0}
g := (color and $00FF00) div $000100; {should return $32}
b := (color and $0000FF);             {should return $00}
newcolor := (b * $010000) + (g * $000100) + b;

If this works, newcolor should hold $0032F0.

A:
You will probably have to use an assembly routine something like...

var
  result : byte;

asm {
	mov cx,8
	mov ah,0
	mov al,<THE BYTE IN BIG ENDIAN FORMAT>
@do_loop
	shr al,1
	shl ah,1
	jcc @dont_set_bit  ; jump carry clear (I'm not sure if this is correct)
	xor ah,1
@dont_set_bit:
	loop do_loop
	mov result,al
}

This is more like pseudo-code (because my assembly's a little rusty).  =
If you want it to be more "portable" then you'd have to use pascal =
instead of inline assembly (you can still use SHL - bit-wise shift left =
and SHR - right).

-----------------------------------------------------------------------------

34. Title in Print Manager
Q:
How do I assign a name to the print job as displayed in print manager?

A:
Printer.Title := 'Your Title Here';

-----------------------------------------------------------------------------

35. Popup menu by clicking left mouse button
Q:
I would like the popup menu to appear when the user clicks with the left
button.

A:
In your circumstances it might be a good idea to swap the left and
mouse right buttons.  Use the following API call to do this

    Function SwapMouseButton(Swap : Boolean) : Boolean;

    Parameters - Swap : Non0Zero to swap buttons or zero to to restore

    Returns a non-zero if meanings reversed; zero if not;

I would like the popup menu to appear when the user clicks with the left
button.

A:
How about setting the AutoPopup property to false, then on your form's
OnClick event call the Popup's Popup Method. Of course you could
leave AuotoPopup which would allow either button to do the trick.

-----------------------------------------------------------------------------

36. Change the number of tabs in a TTabSet at runtime
Q:
I want to be able to change the number of tabs in a TTabSet at runtime.
According to the documentation, this can be done by adding/deleting strings to
the 'Tabs' property.

A:
Assuming that somewhere in your code is a line like:

  TabSet1: TTabSet;  { assume it is in form Form1 }

then the code that you need to clear all of the tab labels is:

  Form1.TabSet1.Tabs.Clear;

In order to add a new tab label just use this code:

  Form1.TabSet1.Tabs.Add('some label');

Please note that I have only qualified the name on the assumption
that you are referencing it from within the same unit where it is
defined [but ignored the possibility that it might be in an event
handler or method that might partially qualify it -- the extra
qualification won't hurt]. If you need to refer to it from some
other unit then add the unit name (and add unit to "uses"), e.g.

  Unit1.Form1.TabSet1.Tabs.Add('a label');

Since "TabSet1.Tabs" is of type TStrings you can also use all of
the methods (AddObject, LoadFromFile, et cetera) that apply to
the TStrings type on that property.

-----------------------------------------------------------------------------

37. Loading a TMemoField into a TMemo component
Q:
I am looking for a good way to load a TMemoField from a DBase table into a 
TMemo component. I do not want to use a DBMemo since I want to control when 
and how edits are saved, etc.
The only way that seems to work consistently is to load it into a DBMemo
component and then assign the lines property of the TMemo to the lines property 
of the DBMemo. This is not what I want to do.

A:
Procedure TMemoToTMemoField;
begin
   TMemoField.Assign( TMemo.Lines );
end;

Procedure TMemoFieldToTMemo;
VAR aBlobStream : TBlobStream;
begin
   aBlobStream := TBlobStream.Create(TMemoField, bmRead);
   TMemo.Lines.LoadFromStream( aBlobStream );
   aBlobStream.Free;
end;

-----------------------------------------------------------------------------

38. CheckBox array - using common event
Q:
In VB I had an array of CheckBox's that used a common event handler.
I could determine and handle which CheckBox caused the event by using 
the INDEX argument passed to the event handler.

A:
Try putting the checkboxes into a TGroupBox component. At run-time (or design 
time) assign the common procedure to the Click event of all the checkboxes.
You could use the Controls array property of TGroupBox to iterate through the 
child TCheckBoxes (and you could typecast them into TCheckBox).  
Something like:
  for i := 0 to GroupBox1.ControlCount -1 do
    if (GroupBox1.Controls[i] as TCheckBox).checked then
       {do something};

A:
You can get the name of the sender as follows:

procedure TMain1.CheckBoxClick(Sender: TObject);
var
     whodidit  : string[63];

begin
     whodidit := TComponent(sender).name;
     end; 


By casting you can get other propeties as well. For instance the tag
property can be very
useful. You can set each checkbox with an ID number at creation time. You
can read the ID at the event handler to identify the sender.

-----------------------------------------------------------------------------

39. Lasso control
Q:
Does anybody know how to implement a lasso (selecting many controls by 
dragging the mouse over them) in Delphi?

A:
Here's a possible approach...

1.  In the OnMouseDown event for the form that you are 'lasso-ing' controls on:

	bMarquee :=  True;		{ set a boolean so that you can
                                          differentiate between decisions that
					  might have to be made during other
					  mouse events }
	ptOrigin := Point( X, Y );	{ get the starting point of the marquee }
	ptMove  := Point( X, Y );	{ initialize the stopping point }

	set the pen and brush attributes here or by calling a common procedure
	that can be reused elsewhere in the Unit.

	Pen.Color := clBlack;
	Pen.Width := 1;
	Pen.Style := psDash;
	Brush.Style := bsClear;

	then draw the marquee rect

	DrawMarquee(ptOrigin, ptMove, pmNotXor );


2.  In the OnMouseMove event for the form...

	if bMarquee = True then begin
	   DrawMarquee(ptOrigin, ptMove, pmNotXor );
	   DrawMarquee(ptOrigin, Point( X, Y ), pmNotXor );
	   ptMove := Point( X, Y );
	   Canvas.Pen.Mode := pmCopy;
	end;


3.  In the OnMouseUp event for the form...

	if bMarquee = True then begin
	   bMarquee := False;
	   DrawMarquee(ptOrigin, Point( X, Y ), pmNotXor );
	   ptMove := Point( X, Y );
	
	   { check for any intersections between the marquee frame and controls }

	   - call the procedure that will highlight ( focus ) the desired controls

	end;


The DrawMarquee procedure...

procedure myForm.DrawMarquee( mStart, mStop : TPoint; AMode : TPenMode);
begin
   Canvas.Pen.Mode := AMode;
   Canvas.Rectangle( mStart.X, mStart.Y, mStop.X, mStop.Y );
end;

-----------------------------------------------------------------------------
