You are on page 1of 32

Creating Events in Delphi 6

Where Do Events Come From?


The general definition of an event is basically any type of occurrence that might result from user
interaction, the system, or from code logic. The event is linked to some code that responds to that
occurrence. The linkage of the event to code that responds to an event is called an event property and is
provided in the form of a method pointer. The method to which an event property points is called anevent
handler.

For example, when the user clicks the mouse button, a WM_MOUSEDOWN message is sent to the Win32
system. Win32 passes that message to the control for which the message was intended. This control can
then respond to the message. The control can respond to this event by first checking to see whether
there is any code to execute. It does this by checking to see whether the event property points to any
code. If so, it executes that code, or rather, the event handler.
The OnClick event is just one of the standard event properties defined by Delphi. OnClick and other
event properties each have a corresponding event-dispatching method. This method is typically a
protected method of the component to which it belongs. This method performs the logic to determine
whether the event property refers to any code provided by the user of the component. For
theOnClick property, this would be the Click() method. Both the OnClick property and
theClick() method are defined by TControl as follows:

TControl = class(TComponent)

private

FOnClick: TNotifyEvent;

protected

procedure Click; dynamic;

property OnClick: TNotifyEvent read FOnClick write FOnClick;

end;
Here is the TControl.Click() method:

procedure TControl.Click;

begin

if Assigned(FOnClick) then FOnClick(Self);

end;

One bit of essential information that you must understand is that event properties are nothing more than
method pointers. Notice that the FOnClick property is defined to be
a TNotifyEvent.TNotifyEvent is defined as follows:

TNotifyEvent = procedure(Sender: TObject) of object;

This says that TNotifyEvent is a procedure that takes one parameter, Sender, which is of the
type TObject. The directive, of object, is what makes this procedure become a method. This
means that an additional implicit parameter that you don't see in the parameter list also gets passed to
this procedure. This is the Self parameter that refers to the object to which this method belongs. When
the Click() method of a component is called, it checks to see if FOnClick actually points to a
method, and if so, calls that method.
As a component writer, you write all the code that defines your event, your event property, and your
dispatching methods. The component user will provide the event handler when using your component.
Your event-dispatching method will check to see whether the user has assigned any code to your event
property and then execute it when code exists.

Event handlers are assigned to event properties either at runtime or at design time. In the following
section, we show you how to create your own events, event properties, and dispatching methods.
Defining Event Properties
Before you define an event property, you need to determine whether you need a special event type. It
helps to be familiar with the common event properties that exist in the Delphi VCL. Most of the time, you'll
be able to have your component descend from one of the existing components and just use its event
properties, or you might have to surface a protected event property. If you determine that none of the
existing events meets your need, you can define your own.

As an example, consider the following scenario. Suppose you want a component containing an event
that gets called every half minute based on the system clock. That is, it gets invoked on the minute and
on the half minute. Well, you can certainly use a TTimer component to check the system time and then
perform some action whenever the time is at the minute or half minute. However, you might want to
incorporate this code into your own component and then make that component available to your users so
that all they have to do is add code to your OnHalfMinute event.
The TddgHalfMinute component shown in Listing 1 illustrates how you would design such a
component. More importantly, it shows how you would go about creating your own event type.
Listing 1—TddgHalfMinute—Event Creation

unit halfmin;

interface

uses

Windows, Messages, SysUtils, Classes, Graphics, Controls,

Forms, Dialogs, ExtCtrls;

type

{ Define a procedure for the event handler. The event property will
be of this procedure type. This type will take two parameters, the

object that invoked the event and a TDateTime value to represent

the time that the event occurred. For our component this will be

every half-minute. }

TTimeEvent = procedure(Sender: TObject; TheTime: TDateTime) of object;

TddgHalfMinute = class(TComponent)

private

FTimer: TTimer;

{ Define a storage field to point to the user's event handler.

The user's event handler must be of the procedural type

TTimeEvent. }

FOnHalfMinute: TTimeEvent;

FOldSecond, FSecond: Word; // Variables used in the code

{ Define a procedure, FTimerTimer that will be assigned to

FTimer.OnClick. This procedure must be of the type TNotifyEvent

which is the type of TTimer.OnClick. }

procedure FTimerTimer(Sender: TObject);

protected
{ Define the dispatching method for the OnHalfMinute event. }

procedure DoHalfMinute(TheTime: TDateTime); dynamic;

public

constructor Create(AOwner: TComponent); override;

destructor Destroy; override;

published

// Define the actual property that will show in the Object Inspector

property OnHalfMinute: TTimeEvent read FOnHalfMinute write FOnHalfMinute;

end;

implementation

constructor TddgHalfMinute.Create(AOwner: TComponent);

{ The Create constructor, creates the TTimer instanced for FTimer. It

then sets up the various properties of FTimer, including its OnTimer

event handler which is TddgHalfMinute's FTimerTimer() method. Notice

that FTimer.Enabled is set to true only if the component is running

and not while the component is in design mode. }

begin
inherited Create(AOwner);

// If the component is in design mode, do not enable FTimer.

if not (csDesigning in ComponentState) then

begin

FTimer := TTimer.Create(self);

FTimer.Enabled := True;

// Set up the other properties, including the FTimer.OnTimer event handler

FTimer.Interval := 500;

FTimer.OnTimer := FTimerTimer;

end;

end;

destructor TddgHalfMinute.Destroy;

begin

FTimer.Free;

inherited Destroy;

end;

procedure TddgHalfMinute.FTimerTimer(Sender: TObject);


{ This method serves as the FTimer.OnTimer event handler and is assigned

to FTimer.OnTimer at run-time in TddgHalfMinute's constructor.

This method gets the system time, and then determines whether or not

the time is on the minute, or on the half-minute. If either of these

conditions are true, it calls the OnHalfMinute dispatching method,

DoHalfMinute. }

var

DT: TDateTime;

Temp: Word;

begin

DT := Now; // Get the system time.

FOldSecond := FSecond; // Save the old second.

// Get the time values, needed is the second value

DecodeTime(DT, Temp, Temp, FSecond, Temp);

{ If not the same second when this method was last called, and if

it is a half minute, call DoOnHalfMinute. }

if FSecond <> FOldSecond then


if ((FSecond = 30) or (FSecond = 0)) then

DoHalfMinute(DT)

end;

procedure TddgHalfMinute.DoHalfMinute(TheTime: TDateTime);

{ This method is the dispatching method for the OnHalfMinute event.

it checks to see if the user of the component has attached an

event handler to OnHalfMinute and if so, calls that code. }

begin

if Assigned(FOnHalfMinute) then

FOnHalfMinute(Self, TheTime);

end;

end.

When creating your own events, you must determine what information you want to provide to users of
your component as a parameter in the event handler. For example, when you create an event handler for
the TEdit.OnKeyPress event, your event handler looks like the following code:

procedure TForm1.Edit1KeyPress(Sender: TObject; var Key: Char);

begin
end;

Not only do you get a reference to the object that caused the event, but you also get a Charparameter
specifying the key that was pressed. Deep in the Delphi VCL, this event occurred as a result of
a WM_CHAR Win32 message that drags along some additional information relating to the key pressed.
Delphi takes care of extracting the necessary data and making it available to component users as event
handler parameters. One of the nice things about the whole scheme is that it enables component writers
to take information that might be somewhat complex to understand and make it available to component
users in a much more understandable and easy-to-use format.
Notice the var parameter in the preceding Edit1KeyPress() method. You might be wondering why
this method wasn't declared as a function that returns a Char type instead of a procedure. Although
method types can be functions, you shouldn't declare events as functions because it will introduce
ambiguity; when you refer to a method pointer that is a function, you can't know whether you're referring
to the function result or to the function pointer value itself. By the way, one function event in the VCL
slipped past the developers from the Delphi 1 days, and now it must remain. This event is
theTApplication.OnHelp event.
Looking at Listing 1, you'll see that we've defined the procedure type TOnHalfMinute as this:

TTimeEvent = procedure(Sender: TObject; TheTime: TDateTime) of object;

This procedure type defines the procedure type for the OnHalfMinute event handler. Here, we
decided that we want the user to have a reference to the object causing the event to occur and
theTDateTime value of when the event occurred.
The FOnHalfMinute storage field is the reference to the user's event handler and is surfaced to the
Object Inspector at design time through the OnHalfMinute property.
The basic functionality of the component uses a TTimer object to check the seconds value every half
second. If the seconds value is 0 or 30, it invokes the DoHalfMinute() method, which is responsible
for checking for the existence of an event handler and then calling it. Much of this is explained in the
code's comments, which you should read over.
After installing this component to Delphi's Component Palette, you can place the component on the form
and add the following event handler to the OnHalfMinute event:
procedure TForm1.ddgHalfMinuteHalfMinute(Sender: TObject; TheTime:
TDateTime);

begin

ShowMessage('The Time is '+TimeToStr(TheTime));

end;

This should illustrate how your newly defined event type becomes an event handler.

Delphi Component Building


Delphi has two kinds of users: end-users (application developers) and component
writers. This session is especially intended for the latter group of Delphi programmers,
who want to start building their own components for Delphi. While Delphi is a great
tool for Client/Server and Rapid Application Building, I think the most important
feature of Delphi is the ability to write components and add them to the component
palette of the environment itself. I strongly believe that a Delphi Component is The
Object of the '90s (and beyond).

OOP
Object Oriented Programming is based on (and extends) established ideas of
Structured Programming, and involves three basic principles: encapsulation,
inheritance and polymorphism.Encapsulation is the concept of placing data and
routines that operate on that data together and combining them to create a structure
(object) that contains both. Inheritance is the concept of new objects that are
derived from existing objects and can add or change the behaviour of their parent.
This is the main feature that leads to re-use of existing code. Finally,polymorphism is
the concept that causes different types of objects derived from the same parent object
to be able to behave differently when instructed to perform a same-named method
with a different implementation.

Although OOP has always been related to claims of code re-use and faster
development cycles, in practice, this has proven to be more often false than true. Two
main problems with practical code-reuse are determining and finding which object(s)
to use for a particular problem, and organising these objects in a usable and accessible
architecture. Delphi solves these problems in three ways. First of all, the component
architecture itself is the foundation for re-use. Second, by placing the re-usable objects
or components in a structured Component Palette, components can be logically
grouped together by type. The Component Palette is merely an organisation tool of the
components, but very important for the use, made possible by the component
architecture itself. Finally, with Delphi we are able to develop components and our
application in the very same environment.

Component Building
Delphi and Delphi Components are built upon the Visual Component Library application
framework. The VCL is already a very rich framework, which becomes clear if we take
a look at the Component Palette of Delphi: dozens of standard Windows controls like
editboxes, static lines, combo and listboxes, but also several advanced custom controls
like grid controls, tab controls, notebook controls, an outliner, OLE controls and data-
aware controls. The list goes on and on, and will go on and on, since Delphi includes
the ability to include new components in the Component Palette!

Creating a new component requires writing a .PAS unit file containing the source code
of the component, which will be compiled to a .DCU file. Additionally, we could include
a .DCR palette bitmap (a renamed .RES file with a 28x28 bitmap with the same name
as the component, to appear in the component palette), maybe even a .DFM form file,
a .HLP help file and a .KWF keyword file. All of these topics wills be covered in this
session. For the moment, however, we will only concentrate on the .PAS component
source file.

Before we can start writing a component ourselves, we have to identify the relevant
classes from VCL. The following class hierarchy shows the most important seven
classes for this task:

TObject
+- TPersistent
+- TComponent
+- TControl
+- TGraphicControl
+- TWinControl
+- TCustomControl
The VCL is not based on the old object model of Borland Pascal, where VMTs are stored
in the Data Segment. Instead, the VCL is based on a new class model (with the
emphasis on class). In this new class model, all object instances are automatically
dynamically allocated on the heap. This means that Delphi treats each class instance
we reference as a pointer to that class instance. It is no longer necessary to explicitly
declare a pointer type or to use the dereference symbol. This greatly reduces the
syntax complexity of ObjectPascal, as pointers have always been a complex topics for
(beginning) programmers.

Every class from the VCL is derived from the TObject root. The class TObject contains
the Create and Destroy methods that are needed to create and destroy instances of
classes. The class TPersistent, derived from TObject, contains methods for reading and
writing properties to and from a form file. TComponent is the class to derive all
components from, as it contains the methods and properties that allow Delphi to use
TComponents as design elements, view their properties with the Object Inspector and
place these components in the Component Palette. If we want to create a new non-
visual component from scratch, then TComponent is the class we need to derive from.
Visual component classes are derived from the TControl class, that already contains
the basic functionality for visual design components, like position, visibility, font and
caption. Derived from TControl are TGraphicControl and TWinControl. The
TGraphicControl contains a Canvas property to make painting easier. The TWinControl
contains a Windows' handle, so it can respond to input (i.e. it can get the input focus).
The TCustomControl is a combination of a TGraphicsControl and a TWinControl; it
contains both a Canvas and a Windows handle, so we can paint on it with ease, and it
can receive the input focus!

Properties, Methods and Events


Components consist of encapsulated properties, methods and events. Properties are
slots that give the component user the illusion of reading or writing the value of a
variable in the component, while the component writer can use properties to hide the
implementation details. In a sense, properties are the user interface to a component.
Methods are procedures and functions that are encapsulated with properties in a
component. Events are like reactions (event handlers) to messages (events) that occur
during execution of the component. Examples of events are OnClick and OnEnter. Both
methods and events can be made dynamic, which gives us the polymorphic ability of
component classes.

Properties have to be read and written, and hence contain a read (Get) and write (Set)
field or method. The Get method is a function that returns the property value, while
the Set method is a procedure that takes as a parameter the new property value. The
Set property method makes a great place to include some data validation rules. An
example of a property "Day" declaration is as follows:

private
FDay: Word;

protected
function GetDay: Word;
procedure SetDay(NewDay: Word);

published
property Day: Word read GetDay write SetDay;
Where GetDay and SetDay are property methods that have to be implemented in the
implementation section of the unit. The keyword private leaves the internal field FDay
only visible to the methods of the current class (or any code in the same unit, by the
way). The new keyword protected ensures us that only classes derived from the
current class can call or override the property methods GetDay and SetDay. The new
keyword published, finally, tells Delphi that the property Day should be visible in the
Object Inspector.

Published properties of components can be stored to and read from streams. This is
what happens in the DFM file, which is a stream file with the values of the properties of
the form, but also the components and properies of the components on the form. This
is why we can only see read/write properties in the Object Inspector; they have to be
read and written from the DFM file! We can specify a default value for a property (for
example, Day = 2). Even when using a default property value, the property still needs
to be initialised to this value (unless the default value is zero, since all initial values are
0 by default). If a property's value it equal to it's default value, then the value is not
streamed to the DFM file. Only if the value differs from the default value then the value
is streamed to the DFM file. Therefore, a thoughtful use of default values for properties
can reduce the size of the DFM file (and loading time of your form).
Design time extensions to the Delphi IDE can be written in the form of Property and
Component Editors. Please see my session on Property and Component Editors for
more information on this topic.
Enough talk for now, let's start with our TTicTacToe game component...

The Engine DLL


The engine part of the tic-tac-toe game component is contained in a dynamic link
library called MAGIC.DLL, originally written quite some time ago in Borland Pascal for
Windows, and now ported to Delphi (single source compilable). This DLL uses the
'magic square' algorithm, which is based on a gameboard with the following internal
layout:

The magic square algorithm simply implies that three fields are a winning combination
if the sum of the identifiers of these fields is 15. Based on this general rule, the
routines inside MAGIC.DLL know how to find a move which at least prevents the
opponent of winning. I implemented the TTT strategy in a generic usable DLL, since I
wanted to ensure the DLL could be used by more than one game component (instance)
at once. But how does the player and DLL know which move applies to which game? I
decided to use a well known technique also used by, for example, Windows: Handles.
Each game starts with a call to NewGame(), which returns a unique Game Handle (or 0
in case of an error). For every move, up and including the EndGame() call, the player
has to supply the unique Game Handle for this particular game. Internally, the DLL has
room for 256 concurrent games, although I've never played more than seven
simultaneously at the same time. Although I have written the DLL some time ago, I
will not show the internals here. This time, our target is primarily to focus on how to
write import units for (possibly other, foreign) DLLs and encapsulate them into new
Delphi components. The internals of the DLL might only distract us from the
encapsulation itself.

The Import Unit


Using MAGIC.DLL by another Delphi application actually introduced a little problem at
first. Instead of getting everything, compared with using a unit, a DLL only exports
functions, and not type definitions, constants or variables. Consequently, we have no
choice but to repeat (from the original source code of MAGIC.DLL) all constant
declarations and type definitions we need to import the DLL functions.

(By the way, most of the time, we'll be faced with a DLL for which only a C header file
is available. In this situation, writing an import unit is even more daring, as we must
convert the C header file to ObjectPascal declarations also! Fortunately, a Delphi IDE
Expert called HeadConv is now available to assist in this conversion process. HeadConv
is part of "Dr.Bob's Collection of Delphi IDE Experts", which can be downloaded from
my homepage or from the DELPHI and BDELPHI32 fora on CompuServe as
file DRBOBxxx.ZIP).

The implicit import unit for the MAGIC.DLL, which we will use for prototyping the
component, is as follows:

{$A+,B-,D-,F+,G+,I-,K+,L-,N-,P-,Q-,R-,S+,T+,V-,W-,X+,Y-}
unit MAGIC;
{
File: MAGIC.IMP
Author: Bob Swart (aka Dr.Bob)
Purpose: implicit import unit for MAGIC.DLL
}
interface
Const
NoneID = 0;
UserID = 1;
CompID = 2;

Type
TPlayer = NoneID..CompID;

Const
NilPlace = 0; { move impossible }
FirstPlace = 1;
LastPlace = 9;

Type
TPlace = FirstPlace..LastPlace;
TMove = NilPlace..LastPlace;

Type
HGame = Word; { 16-bit Handle to a game }

function NewGame: HGame


{$IFDEF WIN32} stdcall {$ENDIF};
procedure EndGame(Game: HGame)
{$IFDEF WIN32} stdcall {$ENDIF};
procedure MakeMove(Game: HGame; ID: TPlayer; Place: TPlace)
{$IFDEF WIN32} stdcall {$ENDIF};
function NextMove(Game: HGame; ID: TPlayer): TMove
{$IFDEF WIN32} stdcall {$ENDIF};
function IsWinner(Game: HGame): TPlayer
{$IFDEF WIN32} stdcall {$ENDIF};
function GetValue(Game: HGame; Place: TPlace): TPlayer
{$IFDEF WIN32} stdcall {$ENDIF};

implementation
Const
DLL = 'MAGIC' {$IFDEF WIN32} +'.DLL' {$ENDIF};

function NewGame; external DLL index 1;


procedure EndGame; external DLL index 2;
procedure MakeMove; external DLL index 3;
function NextMove; external DLL index 4;
function IsWinner; external DLL index 5;
function GetValue; external DLL index 6;

end.

The function NewGame returns a game handle needed to play. The


procedure EndGame gets this game handle, and releases the game in order to re-use
the handle for another game. Procedure MakeMove is used to tell the DLL that for a
certain game, a certain player has made a move on a certain place. The DLL can make
a counter-move at any time using the function NextMove, given a game handle and
player. Note that it is thus possible to let the DLL play against itself. Finally, the last
two functions, IsWinner and GetValue can be used for administration reasons to
query whether or not we have a winner, and how the fields on TTT playing-board are
filled in. Extensions to the functionality we choose not to implement at this time are
MoveBack and Hint (note that a one-level Hint cannot be obtained using NextMove,
since the latter actually moves and we have no move back (or undo) - we will actually
implement this feature later).

The Delphi Component


As said in the introduction, the target of this session is to write a little tic-tac-toe game
component that encapsulates the MAGIC.DLL engine. First, let's think about what this
little component would look like...
The final component would be something with nine game buttons on it. Alternately, the
computer and the user would "click" on one of the buttons to play the game. The
Delphi component would really only need to concern itself with the user/DLL interface
issues, while the tic-tac-toe game algorithm itself would be handled by calls to and
internals of the MAGIC.DLL.

The Component Expert


Although component building is a non-visual task, we can get a some support from the
Delphi Component Expert that we can find in the Repository (when we select File |
New):
If we click on OK after we've selected a New Component, we get the Component
Expert itself:

First of all, we need to supply the class name of our new component: TTicTacToe.
Since we want to create an original Windows (game/custom) control, we must derive
from the basic TWinControl. Finally, the components will initially appear on the Dr.Bob
palette page.
After we press the OK-button, the Delphi Component Expert generates the following
code to start the component building task (note that we've saved the source file to the
unit Ttt.pas already):

unit Ttt;

interface
uses
SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,
forms, Dialogs;

type
TTicTacToe = class(TWinControl)
private
{ Private declarations }
protected
{ Protected declarations }
public
{ Public declarations }
published
{ Published declarations }
end;

procedure Register;

implementation

procedure Register;
begin
RegisterComponents('Dr.Bob', [TTicTacToe]);
end;

end.
The Component Expert makes no assumptions on the number of classes and units we
need for our new component. Therefore, quite a lot of units are included in the uses
clause. In the following listing, I've removed all units which are not needed, and left
only the units that are really needed, including the import unit MAGIC, which was not
included in the first place.
The Component Expert also generated a component template with a few placeholders
for our properties and methods. The private declarations are perfectly suited for the
game property fields, which must remain hidden. The private parts of a component are
only accessible from within the unit itself or from within instances of the class. The
former is one of the reasons why we should put each component in its own unit (so no
component could access the private parts of another component from the same unit).
The access functions to these private property fields are best declared protected (so a
derived class can see and override them). The protected part can be seen as the
developer's interface to the component. The property names themselves are declared
as published, so we can see them in the property inspector. The published parts can
be seen as the designer's user interface to the component. Finally, the public part of
the component contains the public methods and fields from the component, such as
the constructor for example.

The First Step


It should be clear that the generated code from the Component Expert only means to
give us a template to write the things we need. The register part is especially nicely
done for us!
Now, we need a constructor and destructor to obtain and free a game handle from
MAGIC.DLL. A very important thing not to forget is that we must specify the
keyword override with our constructor and destructor. This ensures that the virtual
constructor and destructor of the parent class TWinControl can be called by this child
class TTicTacToe:

unit TTT;
interface
uses SysUtils, Classes, Controls, StdCtrls, Dialogs, Magic;

Type
TTicTacToe = class(TWinControl)
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;

private { Magic DLL handle }


FGame: HGame;
end {TTicTacToe1};

[..]

constructor TTicTacToe.Create(AOwner: TComponent);


begin
inherited Create(AOwner);
FGame := NewGame
end {Create};

destructor TTicTacToe.Destroy;
begin
if (FGame > 0) then EndGame(FGame);
inherited Destroy
end {Destroy};

Note that we've made the FGame handle a private internal field of the TTicTacToe
component, as we don't want anybody to mess with it. The constructor and destructor
will make sure the handle is allocated and freed, and now it's time to move on to
design the actual playing parts of the component.

The Playing Fields


Unlike form designing, component building is a non-visual task. We have to edit the
source file (with the Delphi source editor) to make the changes we need. In this case,
we'd like to add nine playing buttons to the TTicTacToe component. So, we need to
add an array of controls:

private
Button: Array[TPlace] of TButton;
to the TTicTacToe class declaration. In the constructor TTicTacToe.Create we make
sure the buttons are created as follows:
constructor TTicTacToe.Create(AOwner: TComponent);
var ButtonIndex: TPlace; { from MAGIC.DLL }
begin
inherited Create(AOwner);
FGame := NewGame;
for ButtonIndex := Low(ButtonIndex) to High(ButtonIndex) do
begin
Button[ButtonIndex] := TButton.Create(Self);
Button[ButtonIndex].Parent := Self; { important!! }
Button[ButtonIndex].Caption := '';
Button[ButtonIndex].OnClick := ButtonClick;
{ we'll get back to the OnClick event later }
end;
SetBounds(Left,Top,132,132)
end {Create};
Note that although the nine buttons are created in the constructor Create of the
TTicTacToe, it is very important to actually set the parent of the buttons to this control.
Otherwise, they will not be children of our component (and will not be visible at all).
At the end of the constructor, we call SetBounds to give the game component an initial
size from the current position where we dropped it. This will make sure the component
looks good from the first time we drop it on a form. Of course, to make a good
impression, the nine playing buttons will also need to be resized within the parent
game component itself. This leads us to override the SetBounds procedure as follows:
procedure TTicTacToe.SetBounds(ALeft, ATop, AWidth, AHeight: Integer);
Const Grid = 3; { 3x3 buttons }
GridX = 2; { 2 pixels space on X-axis between buttons }
GridY = 2; { 2 pixels space on Y-axis between buttons }
var X,DX,W,Y,DY,H: Word;
begin
Inherited SetBounds(ALeft,ATop,AWidth,AHeight);
X := GridX;
DX := (Width div (Grid * (GridX+GridX))) * (GridX+GridX);
W := DX - GridX;
Y := GridY;
DY := (Height div (Grid * (GridY+GridY))) * (GridY+GridY);
H := DY - GridY;
Button[8].SetBounds(X, Y, W,H);
Button[1].SetBounds(X, Y+DY, W,H);
Button[6].SetBounds(X, Y+DY+DY, W,H);
Inc(X,DX);
Button[3].SetBounds(X, Y, W,H);
Button[5].SetBounds(X, Y+DY, W,H);
Button[7].SetBounds(X, Y+DY+DY, W,H);
Inc(X,DX);
Button[4].SetBounds(X, Y, W,H);
Button[9].SetBounds(X, Y+DY, W,H);
Button[2].SetBounds(X, Y+DY+DY, W,H)
end {SetBounds};
The calculations above are only needed to make sure the nine buttons are properly
aligned with respect to each other and their parent component itself.

Play!
Now that the buttons are all in place, it's time to use the game handle and get the
game started. For this we need to know when a button is clicked (ButtonClick) and we
need to know what to do when the Computer moves (ComputerMove) and when the
User moves (UserMove):

private
FGameEnded: Boolean;

protected
procedure ButtonClick(Sender: TObject);
procedure ComputerMove;
procedure UserMove(Move: TPlace);
If the user clicks on a button, we need to know on which button the user clicked. Since
we used an array of buttons, we can simply walk the array and see if the sender (of
the buttonclick) as TButton equals one of the buttons. The "AS TButton" part is needed
to typecast the Sender TObject to the polymorphic object it actually IS (a TButton in
this case):
procedure TTicTacToe.ButtonClick(Sender: TObject);
var ButtonIndex: TPlace;
begin
for ButtonIndex := Low(ButtonIndex) to High(ButtonIndex) do
if Button[ButtonIndex] = Sender AS TButton then
UserMove(ButtonIndex)
end {ButtonClick};
The UserMove consists of nothing more than telling the DLL that the user decided to
move to this spot. Of course, we need to check whether or not the game is already
over, and whether or not the space is already occupied. We can use several functions
from MAGIC.DLL (supplying the game handle) for this task, namely IsWinner, GetValue
and MakeMove.
procedure TTicTacToe.UserMove(Move: TPlace);
begin
if IsWinner(FGame) <> NoneID then
begin
if IsWinner(FGame) = UserID then
MessageDlg('You have already won!', mtInformation, [mbOk], 0)
else
MessageDlg('I have already won!', mtInformation, [mbOk], 0)
end
else
begin
if FGameEnded then
MessageDlg('The game has already ended!', mtInformation, [mbOk],
0)
else
begin
if GetValue(FGame, Move) <> NoneID then
MessageDlg('This place is occupied!', mtWarning, [mbOk], 0)
else
begin
Button[Move].Caption := 'X';
MakeMove(FGame,UserID,Move);
if IsWinner(FGame) = UserID then
MessageDlg('Congratulations, you have won!',
mtInformation, [mbOk], 0)
else
ComputerMove
end
end
end
end {UserMove};
At the end of the UserMove, it's the computer's turn to move. The computer will
automatically detect whether or not the game is already over. Otherwise, it will call the
function NextMove from MAGIC.DLL to determine the next best move, according to the
magic square algorithm:
procedure TTicTacToe.ComputerMove;
var Move: TMove;
begin
if IsWinner(FGame) = NoneID then
begin
Move := NextMove(FGame,CompID);
if Move = 0 then
begin
FGameEnded := True;
MessageDlg('Neither has won, the game is a draw!',
mtInformation, [mbOk], 0)
end
else
begin
MakeMove(FGame,CompID,Move);
Button[Move].Caption := '0';
if IsWinner(FGame) = CompID then
MessageDlg('I have won!', mtInformation, [mbOk], 0)
else
begin
Move := NextMove(FGame,UserID);
if Move = 0 then
begin
FGameEnded := True;
MessageDlg('Neither has won, the game is a draw!',
mtInformation, [mbOk], 0)
end
else
Button[Move].SetFocus { hidden hint... }
end
end
end
end {ComputerMove};
Note that right after making the ComputerMove with MakeMove, the routine calls the
NextMove with the UserID to find out whether or not the game has ended. If not, the
best move for the user is given as a hint by giving the corresponding button the focus
with SetFocus. By adding the Boolean GameEnded property and an initial start button
over the control, we get the first complete working component:

Notice the button in the middle of the top row who got the focus right after the
computer move!

Adding Game Properties


While the game seems to run fine, we'd like to be able to put more control in it. For
starters, the computer always starts to move. Also, we don't really like the 'X' vs '0'
characters that are always used. Why not user-definable characters? Let's use
properties to define these at design time or runtime of our TTicTacToe game
component?

Well, that's exactly what we'll do. We can add three new private property fields to the
class declaration; FUserStarts, FCompChar and FUserChar. Next, we write three
property methods to actually set the new value of FUserStarts, FCompChar and
FUserChar. The first is the easiest, as it's either True or False. The last two are more
difficult, as we don't want the user and computer to play with the same
character (otherwise it'd be really easy to win, right?). So, we have to write one line of
code to check whether CompChar already has the value we want to assign to
FUserChar and vice versa:

procedure TTicTacToe.SetUserChar(Value: Char);


begin
if Value = FCompChar then
begin
MessageDlg('Character '+Value+' already in use by CompChar!',
mtError, [mbOk], 0)
end
else
FUserChar := Value
end {SetUserChar};
Having done all this, we're now completely in control of the new game component, and
are ready to install it for the first time.

Component Bitmap
But before we install it, it would be a good idea to design a bitmap for it, to identify it
more easily on the Component Palette. We can use the Image Editor that is provided
by Delphi itself, but personally I prefer to use Resource Workshop for this task:
Using Resource Workshop we can make a nice bitmap palette to use for this
component. It needs to be a 16-colour bitmap with dimensions that are no larger than
28 by 28 pixels. Other than that, it's important to give the bitmap the same name as
the component itself (in all upper case letters), and to save the resource file with a
.DCR extension. Actually, saving it as a .RC resource source file might be even better,
as we can then use the 16-bit and 32-bit command line resource compilers BRCC and
BRCC32 to compile the resource source file to either a 16-bit or a 32-bit binary
resource file. Depending on the version of Delphi, we need either the 16-bit or the 32-
bit DCR file to be in the same directory as the component source or .DCU file before we
install it.

Installation
We can install the TTicTacToe component onto the Delphi Component Palette with the
menu-option Component | Install. A dialog will follow in which we need to select the
source code for the component (TTT.PAS in this case).
After we click on the OK-Button, the entire Component Library is recompiled, and - if it
recompiled without errors - the old one is unloaded and the new version is loaded into
Delphi, to make our newly added Component immediately available to us (on the
Dr.Bob palette page). Make sure the MAGIC.DLL is in the Windows' search path.
Once we've added the component to the palette, we can drop it on a form and look at
it throught the Object Inspector. The new properties are ready to be modified:

Exceptional!
The SetXX methods for our properties (last paragraph) used MessageDlgs to notify the
user that an invalid value was assigned to CompChar or UserChar (i.e. a value that
was already in use by the other party). While this may seem adequate at first, it might
be a pain when we set these properties at run-time (from another part of a program).
In fact, even from the Property Inspector it's not the behaviour we'd really want (try
for yourself and see what happens).
Delphi supports exceptions, so why not use them? If we just derive a new type of
exceptions EBadChar from Exception, and raise one whenever we try to set a "bad
char" to one of the playing properties, then the TTicTacToe component gets the
behaviour it deserves. An exception is raised (and can be "caught" by the calling
program, whether it's the Property Inspector or a playing program that uses the game
component. If the exception is not caught by the try-except block, a messagebox in
fact similar to the MessageDlg pops up to tell us something went wrong!).
The code is as follows:

{$DEFINE EXCEPTIONS}

{$IFDEF EXCEPTIONS}
Type
EBadChar = class(Exception);
{$ENDIF EXCEPTIONS}

procedure TTicTacToe.SetUserChar(Value: Char);


begin
if Value = FCompChar then
begin
{$IFDEF EXCEPTIONS}
raise EBadChar.Create(Value+' already in use by CompChar!')
{$ELSE}
MessageDlg('Character '+Value+' already in use by CompChar!',
mtError, [mbOk], 0)
{$ENDIF}
end
else
FUserChar := Value
end {SetUserChar};
And the result (when trying to assign the UserChar to the same value as CompChar) is
the following exception:

Since some Delphi users might find exceptions still a bit hard to use, I've added an
$IFDEF clause, so we can experiment with them (on and off).

Explicit Import Units


The TTicTacToe component we've built so far used an implicit import unit for the
MAGIC.DLL. This means that the MAGIC.DLL needs to be present if we want to use the
component. It also means that the MAGIC.DLL needs to be present when the
component is added to the component palette! This actually means that the
Component Library will not load when the MAGIC.DLL is not available (i.e. when we've
accidently deleted MAGIC.DLL, the whole Component Library will be unusable).
It is possible to check for a DLLs presence before attempting to use it. This way, we
can remove the implicit usage of the MAGIC.DLL, and make it an explicit dependency,
which will enable the Component Library to load, but the component to fail (with a
graceful exception) whenever MAGIC.DLL is not available. We have to change all
procedure and functions declarations (from the first listing) to procedure and function
variable declarations. By eliminating all the "external DLL index X;" clauses, we in fact
eliminate the implicit reference to (and the automatic attempt to) load the
MAGIC.DLL.
We load the MAGIC.DLL with LoadLibrary. If the resulting handle is > 32, we can use it
to instantiate the procedure and function variables using GetProcAddress:

{$A+,B-,D-,F+,G+,I-,K+,L-,N-,P-,Q-,R-,S+,T+,V-,W-,X+,Y-}
unit MAGIC;
{
File: MAGIC.PAS
Author: Bob Swart (aka Dr.Bob)
Purpose: explicit import unit for MAGIC.DLL
}
interface
Const
NoneID = 0;
UserID = 1;
CompID = 2;

Type
TPlayer = NoneID..CompID;

Const
NilPlace = 0; { move impossible }
FirstPlace = 1;
LastPlace = 9;

Type
TPlace = FirstPlace..LastPlace;
TMove = NilPlace..LastPlace;

Type
HGame = Word; { 16-bit Handle to a game }

Const
MagicLoaded: Boolean = False; { presume nothing! }

var NewGame: function: HGame


{$IFDEF WIN32} stdcall {$ENDIF};
EndGame: procedure(Game: HGame)
{$IFDEF WIN32} stdcall {$ENDIF};
MakeMove: procedure(Game: HGame; ID: TPlayer; Place: TPlace)
{$IFDEF WIN32} stdcall {$ENDIF};
NextMove: function(Game: HGame; ID: TPlayer): TMove
{$IFDEF WIN32} stdcall {$ENDIF};
IsWinner: function(Game: HGame): TPlayer
{$IFDEF WIN32} stdcall {$ENDIF};
GetValue: function(Game: HGame; Place: TPlace): TPlayer
{$IFDEF WIN32} stdcall {$ENDIF};

implementation
{$IFDEF WINDOWS}
uses WinProcs;
Const SEM_NoOpenFileErrorBox = $8000;
{$ELSE}
uses WinAPI;
{$ENDIF}

var SaveExit: pointer;


DLLHandle: Cardinal;

procedure NewExit; far;


begin
ExitProc := SaveExit;
FreeLibrary(DLLHandle)
end {NewExit};

begin
{$IFDEF WINDOWS}
SetErrorMode(SEM_NoOpenFileErrorBox);
{$ENDIF}
DLLHandle := LoadLibrary('MAGIC.DLL');
if DLLHandle >= 32 then
begin
MagicLoaded := True;
SaveExit := ExitProc;
ExitProc := @NewExit;
@NewGame := GetProcAddress(DLLHandle,'NEWGAME');
@EndGame := GetProcAddress(DLLHandle,'ENDGAME');
@MakeMove := GetProcAddress(DLLHandle,'MAKEMOVE');
@NextMove := GetProcAddress(DLLHandle,'NEXTMOVE');
@IsWinner := GetProcAddress(DLLHandle,'ISWINNER');
@GetValue := GetProcAddress(DLLHandle,'GETVALUE')
end
end.

Note that this import unit can be compiled by Delphi 1 and 2 (and actually also by
Borland Pascal for a Windows or DPMI target). There are a few differences in
platforms, but other than that the import unit remains the same. This means that as
long as we have the correct 16-bit or 32-bit version of the DLL available, we can
actually use it with any version of Delphi on any platform (with a single source import
unit)!

Final Construction
The only thing we need to do now is to make sure in our TTicTacToe.StartButtonClick
routine that MagicLoaded is True before we try to attempt to use the imported
functions from MAGIC.DLL (which will not be available, of course, when the DLL is not
loaded).

procedure TTicTacToe.StartButtonClick(Sender: TObject);


var ButtonIndex: TPlace;
begin
if MagicLoaded then { explicit load of DLL succeeded! }
begin
FGame := NewGame;
FGameEnded := False;
StartButton.Visible := False;
for ButtonIndex := Low(ButtonIndex) to High(ButtonIndex) do
Button[ButtonIndex].Visible := True;
if UserStarts then
MessageDlg('You may start...', mtInformation, [mbOk], 0)
else
ComputerMove
end
else
{$IFDEF EXCEPTIONS}
raise EDLLNotLoaded.Create('MAGIC.DLL could not be loaded!')
{$ELSE}
MessageDlg('Error loading MAGIC.DLL...', mtInformation, [mbOk], 0)
{$ENDIF}
end {ButtonClick};
Now, the Component Library and hence the entire component palette will be
unaffected when the MAGIC.DLL is removed (try it if you want). The TTicTacToeControl
component will show a MessageDlg or an exception to tell us that something went
wrong when loading the MAGIC.DLL, and that's all there is to it!

By the way, if we raise an exception, then the IDE will show the following message
first (which we can un-set if we un-check the "Break on Exception" option):

Adding Custom Events


In the previous examples, we have examined the process of writing our own
component based on a DLL engines, and adding a custom bitmap to it. In this part,
we'll focus on adding custom events and event handlers to our components. Events
consist of two parts: an event signaller and the event handler. The signaller must
make sure that the component somehow gets a message of some sort to indicate that
some condition has become true, and that the event is now born. The event handler,
on the other hand, starts to work only after the event itself is generated, and responds
to it by doing some processing of itself. Event signallers are typically based on virtual
(or dynamic) methods of the class itself (like the general Click method) or Windows
messages, such as notification messages. Event handlers are typically placed in event
properties, such as the OnClick or OnChange event handler property. If event handlers
are published, then the user of the component can enter some event handling code
that is to be executed when the event is fired.

For the TTicTacToe game component we can think of at least two events: winning and
losing. And we can define two event handlers: OnWin and OnLose.

Event Handlers
Event Handlers are methods of type Object. This means that they can be assigned only
to class methods, and not to ordinary procedures or functions. Consider the type
TNotifyEvent for the most general of event handlers:

TNotifyEvent = procedure (Sender: TObject) of object;


The TNotifyEvent type is the type for events that only have the sender as parameter.
These events simply notify the component that a specific event occurred at a specific
TObject (the sender). For example, OnClick, which is of type TNotifyEvent, notifies the
control that a click event occurred on the control Sender. If the parameter Sender
would be omitted as well, then we'd only know that a specific event had occurred, but
not at which control. Generally, we do want to know for which control the event just
occurred, so we can act on the control (or on data in the control). As mentioned
before, Event Handlers are placed in event properties, and they appear on a separate
page in the Object Inspector (to distinguish them from the 'normal' properties). The
basis on which the Object Inspector decides to split these two kinds of properties is the
"procedure/function of Object" part. The "of object" part is needed, since we get the
error message "cannot publish property" if we (try to) omit it, as can be experimented
using the following unit:

unit BobEvent;
interface
uses
SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,
Forms, Dialogs, StdCtrls;

Type
TEventNoObject = procedure;
TEventOfObject = procedure of object;

TEventComponent = class(TComponent)
private
{ Private declarations }
FEventNo: TEventNoObject;
FEventOf: TEventOfObject;

published
{ Published declarations }
property OnEventNoObject: TEventNoObject read FEventNo write
FEventNo;
{ error: this property cannot be published }
property OnEventOfObject: TEventOfObject read FEventOf write
FEventOf;
end;

procedure Register;
implementation

procedure Register;
begin
RegisterComponents('Dr.Bob', [TEventComponent])
end {Register};
end.

All this experimenting leads to the conclusion that we need to add two private fields
FOnWin and FOnLose of type TNotifyEvent to the TTicTacToe game component. Apart
from these two private fields, we also need to publish two properties OnWin and
OnLose of type TNotifyEvent who are connected to the private fields:

private { game properties }


FOnLose: TNotifyEvent;
FOnWin: TNotifyEvent;

published { user interface }


property OnWin: TNotifyEvent
read FOnWin write FOnWin;
property OnLose: TNotifyEvent
read FOnLose write FOnLose;

Event Signallers
Event signallers are needed to signal to an event handler that a certain event has
occurred, so the event handler can perform its action. In case of the OnWin and
OnLose events, we can just go to the source code for the TTicTacToe component and
look for the current MessageDlgs that tell the user that he's lost or won. We need to
change this code to check for tha availability of an event, and if so use the event
instead:

...
if IsWinner(Game) = UserID then
if Assigned(FOnWin) then OnWin(Self) { computer lost }
else
MessageDlg('Congratulations, you have won!', mtInformation, [mbOk],
0)
else
ComputerMove
After we've implemented all this, the events page of the TTicTacToe component is as
follows:
Component Help
I'm sure that the code for the TTicTacToe component hold little surprises for you; it's
just another component wrapper based on an explicit import unit. But what about the
casual Delphi user? How will the component user (instead of builder) react if he sees
the published properties? Personally, whenever I'm not sure about something, I always
hit the F1 key to get help on the component itself or one of its properties. Only, since
this is but a "third-party" custom component, Delphi cannot just make up some help
for us and instead responds with the rather disturbing message that no context
sensitive help can be found for your component.

OK, so this may just be the end of the usage period of my TTicTacToe component,
right? Wrong! Delphi is an open enough environment to let us even install our own
Component Help! And that's what we'll be doing in the rest of this article.

WinHelp
First of all, we need to create a WinHelp skeleton file (using a WinHelp Authoring tool
like ForeHelp) with only a few topics (each topic gets its own page with contents):

TTicTacToe

 constructor Create
 methods (popup page)
o procedure UserMove
(other methods)...
 properties (popup page)
o property UserChar
(other properties)...
 events (popup page)
o event OnWin
(other events)...
I used ForeHelp 1.04 to generate the winhelp file skeleton. A prime page for the
TTicTacToe components with three popup pages; properties, methods and events, and
one jump to the constructor. The three popup pages would each have some entries.
Consequently, the way the TTicTacToe component's winhelp works is comparable to
the general Delphi helpfiles (just drop a TEdit component and hit F1 to see the general
Component Help outline of Delphi itself).

After I generated the winhelp skeleton, I saved the project, and edited the .RTF file
with WinWord 2.0c (any RTF-editor is valid). Using WinWord, I could enter the
contents of the eight topics, and more important, I could add the special "B"-keywords
that Delphi needs in order to make your winhelp file really integrated with the Delphi
winhelp files. The "B"-keywords are only needed for the three topics that actually
integrate with Delphi, which are - again - the TTicTacToe main page and the three
properties, methods and events popups. The TTicTacToe main topic page needs a "B"-
footnote that says "class_TTicTacToe", i.e. the class name with "class_" as prefix. Also,
the property topics need "B"-footnotes with the name of the property and the "prop_"
prefix, and the events topics need "B"-footnotes with the name of the event and the
"event_" prefix. For class specific properties, we need to include the class name as
well, i.e. "prop_TTicTacToeUserChar".

Keywords
After we've added the three "B"-footnotes to the winhelp file (and even before we've
actually written the contents of the winhelp file), we can generate the keywords from
this file that are needed to integrate with the Delphi multihelp environment. At this
point, Delphi's own Component Writer's Guide seems to be a little out of date. First of
all, the KWGEN application is a Windows application, and not a DOS one anymore.
Second, we don't need to put the keyword file in the same directory as our compiled
unit and helpfile, as we'll see shortly. KWGEN is a Windows 3.1 application that allows
us to browse for any .HPJ file. It then opens the corresponding .RTF file and scans -
among others - for the "B"-footnotes to generate a special .KWF keyword file.

Installation
As I've said before, we don't need to put the generated .KWF file next to the compiled
unit and helpfile. Instead, we seem to be forced to place this file in the \DELPHI\HELP
directory where the other Delphi .KWF files can be found (such as DELPHI.KWF,
WINAPI.KWF and CWG.KWF). Now, we can use HELPINST.EXE to generate the Delphi
MultiHelp master index DELPHI.HDX file which contains references to all .KWF files in
the list. Remember to close Delphi before you attempt to do this, and to make a
backup of the DELPHI.HDX file (so if something goes wrong, you can always restore
your masterindex). The HELPINST program will start in the DELPHI\HELP directory, by
the way, while the DELPHI.HDX file resides in the \DELPHI\BIN directory. Just to let
you know... I found that if I place the TTicTacToe.KWF file anywhere else, then the
second time I fire up HELPINST it would say that the TTicTacToe.KWF file was not
found, so I had to enter it again. I overcame this problem by placing all .KWF files in
the same directory; not something I have a problem with anyway.

For the TTictacToe.HLP files holds a similar story. The Delphi Component Writer's Guide
recommends to place this helpfile in the same directory as the compiled component
unit, but I found that the easiest - and default - way for the TTicTacToe.HLP file to be
found was to place it inside the \DELPHI\BIN directory. If we want to place it anywhere
else, we need to modify the WINHELP.INI file (in the \WINDOWS directory) and add a
line that reads (for example):
TTicTacToe.HLP=C:\DELPHI\DRBOB\TTicTacToe.HLP

Component Help
Well, now that we've generated a keyword file and integrated it into the Delphi
MultiHelp masterindex, all we need to do is to finish our winhelp file itself, and place it
in the correct directory (\DELPHI\BIN by default). Then we can activate the TTicTacToe
component help in several ways:

 Drop a TTicTacToe component on a form and press F1 (TTicTacToe main


helppage)
 Go to Object Inspector on property "UserChar" and press F1 (property help)

Conclusions
In this session, we have seen just about every aspect of Delphi Component building.
We've seen how to build our own components for Delphi, how to add properties,
methods and custom events to them, how to wrap them around DLLs, how to install
them, how to design a palette bitmap and write on-line help to support the component
user.

Bibliography
If you want more interesting and deep-technical information on the Art of Component
Building using Delphi, then you should check out my articles in The Delphi Magazine or
the book The Revolutionary Guide to Delphi 2, ISBN 1-874416-67-2 published by
WROX Press.

You might also like