An event is the same a a function pointer for those that use for example C or C++. Delphi have a lot of predefined events. For example a button can have OnClick, OnExit etc. It's easy to use them as Delphi IDE helps you with that.
If you use custom events it can help you break dependencies between classes. This make it easier to write tests for the code and refactor it.
This illustrate a form that create and show a dialog. This dialog may also be called from 2 other forms. In the dialog there are special cases to handle this.
First look at the old code before the change:
frmBook.pas
procedure TBookForm.OrderSearch(Sender: TObject);
var
vForm: TFindParcelForm;
begin
vForm := TFindParcelForm.Create(self);
vForm.ShowModal;
end;
procedure TBookForm.OrderNew(Sender: TObject);
begin
// Code to make new order
end;
frmFindParcel.pas
type TFindParcelForm = class(TForm) // Som declarations
end;
// Dependencies to owner
uses
frmBook,
frmPlan,
frmOrg;
begin
// Block1 of code here
if owner is TBookForm) then
(Owner as TBookForm).OrderNew(self)
else if Owner is TPlanForm then
(Owner as TPlanForm).OrderNew(self)
else if Owner is TOrgForm then
(Owner as TOrgForm).OrderNew(self)
// Block2 of code here
end;
Let's now use the predefined event TNotifyEvent. It is defined in VCL Classes.pas:
TNotifyEvent = procedure(Sender: TObject) of object;
procedure TBookForm.OrderSearch(Sender: TObject);
var
vForm: TFindParcelForm;
begin
vForm := TFindParcelForm.Create(self);
vForm.OnNewOrder := OrderNew;
vForm.ShowModal;
end;
// Have the same signature as TNotifyEvent
procedure TBookForm.OrderNew(Sender: TObject);
begin
// Code to make new order
end;
frmFindParcel.pas
type TFindParcelForm = class(TForm) // Some declarations private fNewOrder: TNotifyEvent; public property OnNewOrder: TNotifyEvent read fNewOrder write fNewOrder;
end;
procedure TFindParcelForm.MakeOrderClick(Sender: TObject);
begin
// Block1 of code here
if Assigned(OnNewOrder) then
OnNewOrder(Self);
// Block2 of code here
end;
So what happened was that we have broken the dependency from FrmFindParcel.pas to the owners TBookForm, TPlanForm and TOrgForm. This means that it is now easier to test frmFindParcel as a separate unit. Method MakeOrderClick is now simplified and is not aware of the owner. If else is also gone.
But we don't need to stop here. With a small addition we can make own custom events as the blog title suggests.
type
TSetOrder = procedure(aOrder: TOrder; aPrice: Double) of objects;
TGetOrder = function(): TOrder of objects;
TFindParcelForm = class(TForm) // Some declarations private fNewOrder: TNotifyEvent;
fOnSetOrder: TSetOrder;
fOnGetOrder: TGetOrder;
public
property OnNewOrder: TNotifyEvent read fNewOrder write fNewOrder;
property OnSetOrder: TSetOrder read fOnSetOrder write fOnSetOrder;
property OnGetOrder: TGetOrder read fOnGetOrder write fOnGetOrder;
end;
We added 2 new events OnSetOrder that is a procedure with 2 parameters. And OnGetOrder that just return an order.
Usage:
frmBook.pas
procedure TBookForm.OrderSearch(Sender: TObject);
var
vForm: TFindParcelForm;
begin
vForm := TFindParcelForm.Create(self);
vForm.OnNewOrder := OrderNew;
vForm.OnSetOrder := SetActiveOrder;
vForm.OnGetOrder := GetActiveOrder;
vForm.ShowModal;
end;
procedure TBookForm. SetActiveOrder(aOrder: TOrder; aPrice: Double);
begin
// Code to set Active Order
end;
function TBookForm. GetActiveOrder: TOrder;
begin
// Code return active Order
end;
procedure TBookForm.OrderNew(Sender: TObject);
begin
// Code to make new order
end;
frmFindParcel.pas
procedure TFindParcelForm.TestOrderClick(Sender: TObject);
var
vOrder, vOrder2: TOrder;
begin
// Block1 of code here
vOrder := MakeOrder;
if Assigned(OnSetOrder) then
OnSetOrder(vOrder);
if Assigned(OnGetOrder) then
vOrder2 := OnGetOrder;
// Block2 of code here
end;
So to break dependencies and make code more testable, own custom events are a powerful tool to accomplish that.