Title: Supporting events in your own classes
Question: How can I add custom events to my classes?
Answer:
Steps to be taken:
1) Define an event signature. Events are pointers to functions / procedures. Correctly speaking, they are pointers to methods - we are dealing with an object oriented language! A basic event definition might look like this:
type
TChangeEvent = procedure(Sender: TObject) of object;
You may pass any parameters to your event methods, so the following code is perfectly ok:
type
TDateChanged = procedure(MyBirthDay, YourBirthDay: TDateTime; ABoolean: boolean) of object;
2) Augment your class with event support code. Classes support events via properties. Yes, events are properties! You need a private field for storing a reference to the event handler your user assigns, and a property definition. If you like, you can have a plain property definition (writing directly to your private field) or introduce a pair of set/get methods:
type
TMyTestObject = class
private
FChange: TChangeEvent;
FChange2: TChangeEvent;
procedure SetChange(Value: TChangeEvent);
public
property OnChanged1: TChangeEvent read FChange write FChange; //plain property definition
property OnChanged2: TChangeEvent read FChange2 write SetChange2; // property definition with set method
end;
[...]
procedure TMyTestObject.SetChange(Value: TChangeEvent);
begin
if FChange2 Value then
FChange2 := Value;
end;
3) Now, you should call the event procedure everywhere in your class where appropriate. Let's asume a method "Work" that does some computations:
procedure TMyTestObject.Work;
begin
// (compute something important)
FChange(self);
end;
4) STOP! Don't forget to check for NIL! Since your users are not obliged to use your event, you will quite probably face the situation of having a nil pointer in your private event field. In this case, you will produce a wonderful AV with the above code fragment. So, before calling any event routine, you should check wether the event is assigned or not:
procedure TMyTestObject.Work;
begin
// (compute something important)
if Assigned(FChange) then
FChange(self);
end;
5) It is a good practice to factor out the code that checks for events pointing to nil:
procedure TMyTestObject.DoChange;
begin
if Assigned(FChange) then
FChange(Self);
end;
procedure TMyTestObject.Work;
begin
// (compute something important)
DoChange;
end;
6) Now, you can create an object of your class and assign an event procedure. See the sample code how to do this.
Sample code
Your code should look like this:
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls;
type
(* type of event gets defined here.
you don't need to specify the "Sender: TObject", if you don't need to know who the
sender is. however, supplying this parameter is a good habit.
if you want to provide information about the changed value, put as many additional
parameters "on top". Here, I have only supplied an integer parameter, since I wasn't
sure which params would be interesting to you *)
TChangeEvent = procedure(Sender: TObject) of object;
TMyTestObject = class
private
(* you need a local store for your event in your class. this is normally done within
the private section, so to keep classes that derive from your class away from
tampering with your events *)
FChange: TChangeEvent;
FMyBoolean: boolean;
protected
(* this procedure should be called whenever you want to signal a change to your
"subscribers", as we like to call it. if you are into design patterns: this is a
"template method". *)
procedure DoChange;
procedure SetMyBoolean(AValue: boolean);
public
published
(* this is where you make your event public to the rest of the world. you could also
put it into the "public" section, if you like, but this would hide your event from
mr. object inspector.
with the "read" and "write" keywords, you define how access to your local
store is achieved. you could also write an extra "accessor" function, but this
is rather uncommon for events.*)
property OnChanged: TChangeEvent read FChange write FChange;
property MyBoolean: boolean read FMyBoolean write SetMyBoolean;
end;
TForm1 = class(TForm)
Button1: TButton;
Button2: TButton;
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
private
{ Private declarations }
procedure ItHasChanged(Sender: Tobject);
public
{ Public declarations }
end;
var
Form1: TForm1;
_myTest: TMyTestObject;
implementation
{$R *.DFM}
procedure TMyTestObject.DOChange;
begin
(* if your event isn't assigned in the client, the FChange field will be nil.
it is a prerequisite in events programming to check wether something is
nil before calling on it. *)
if Assigned(FChange) then
FChange(Self);
end;
procedure TMyTestObject.SetMyBoolean(AValue: boolean);
begin
FMyBoolean := AValue;
DoChange;
end;
procedure TForm1.ItHasChanged(Sender: Tobject);
begin
ShowMessage('yo. it has changed!');
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
_myTest := TMyTestObject.Create;
_myTest.OnChanged := ItHasChanged;
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
_myTest.MyBoolean := true;
end;
end.