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 1TddgHalfMinuteEvent 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 Char parameter 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 the TApplication.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 the TDateTime 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.