Mega Code Archive

 
Categories / Delphi / Examples
 

Supporting events in your own classes

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.