Programmers who have been working in Windows for a long time probably do not need to explain the meaning of the term "event". This environment itself and the programs written for it are controlled by events that arise as a result of the effects of the user, computer hardware or other programs. The event news is a Windows message received by what is known as a window function. There are hundreds of such messages, and, by and large, to write a program for Windows means to identify and describe the reaction to some of them.
Working with so many messages, even with a directory at hand, is not easy. Therefore, one of the great advantages of Delphi is that the programmer is spared the need to work with Windows messages (although he has such an opportunity). Typical events in Delphi are no more than two dozen, and all of them have a simple interpretation that does not require deep knowledge of the environment.
Let's look at how events are implemented at the Object Pascal language level. An event is a procedural type property designed to create a user's response to an input:
property OnMyEvent: TMyEvent read FOnMyEvent write FOnMyEvent;
Here, FOnMyEvent is a procedural type field that contains the address of some method. To set such a property to a value is to tell the object the address of the method that will be called at the time the event occurs. These methods are called event handlers. For example, a record:
Application.OnActivate: = MyActivatingMethod;
Means that when the Application object (the same name as the object corresponding to the running application) is invoked, the MyActivatingMethod handler method will be called.
Inside the Delphi run-time library, calls to event handlers are found in methods that process Windows messages. After performing the necessary steps, this method checks whether the handler address is known and, if so, calls it:
if Assigned(FOnMyEvent) then FOnMyEvent(Self);
Events have a different number and type of parameters depending on their origin and purpose. Common to all is the sender parameter - it points to the source object of the event. The simplest type, TNotifyEvent, has no other parameters:
TNotifyEvent = procedure (Sender: TObject) of object;
The type of method used to notify you when a key is pressed instructs you to pass to the programmer the code for that key about the movement of the mouse—its current coordinates, etc. All events in Delphi are preceded by the prefix On: onCreate, onMouseMove, onPaint, and so on.
In the field of any event, you will receive a blank method of the desired type in the program. In this case, its name will consist of the name of the current component and the name of the event (without the on prefix), and it will refer to the current form. Suppose, for example, form1 has the text Label1. Then a preset of the TFormi.Label1click method will be created to handle the click (Onclick event):
procedure TForml.LabellClick(Sender: TObject); begin end;
Because events are properties of an object, you can change their values at any time during program execution. This wonderful feature is called delegation. You can at any time take ways to respond to events from one object and assign (delegate) them to another:
Objectl.OnMouseMove: = Object2.OnMouseMove;
The principle of delegation avoids the time-consuming process of generating new child classes for each specific case by replacing it with a simple substitution of procedures. Optionally, you can choose from several possible event handlers.
But what mechanism allows you to replace handlers, because these are not just procedures, but methods? The concept of a method pointer that exists in Object Pascal comes in handy here. The difference between a method and a procedure is that in addition to the parameters explicitly described, the method is always implicitly passed a pointer to the instance of the class that called it (the self variable). You can describe a procedural type that will be compatible in assignment with the method (i.e., to provide for the receipt of self). To do this, add the reserved words of object to the procedure description. A pointer to a method is a pointer to such a procedure.
type TMyEvent = procedure(Sender: TObject; var AValue: Integer) of object; TlstObject = class; FOnMyEvent: TMyEvent; property OnMyEvent: TMyEvent read FOnMyEvent write FOnMyEvent; end; T2ndObject = class; procedure SetValuel(Sender: TObject; var AValue: Integer); procedure SetValue2(Sender: TObject; var AValue: Integer); end; … var Objl: TlstObject; Obj2: T2ndObject; begin Objl: = TlstObject.Create; Obj2: = T2ndObject.Create; Obj1.OnMyEvent: = Obj2.SetValuel; Obj1.OnMyEvent: = Obj2.SetValue2; … end.
This example shows that you can assign methods of other classes when delegating. Here, the OnMyEvent event handler of the Obj1 object is the SetValue1 and Setvaiue2 methods of the Obj2 object in turn.
Event handlers can't just be done as procedures—they must be someone else's methods. But they can be "given" to some other object. Moreover, for these purposes it is possible to describe and create a special object. Its sole purpose is to be the carrier of methods, which are then delegated to other objects. Of course, such an object should not forget to create before using its methods, and at the end - to destroy. You can avoid doing this by declaring methods to be methods of a class, which will be discussed in a later section.
We have now solved the problem of using several different handlers for a particular event for one object. But no less often you need to solve the inverse problem – how to use the same handler for different events of different objects?
If no "personification" of the object that called the method is needed, everything is done trivially and the problem does not arise. The simplest example: in modern programs, the main functions are duplicated twice - in the menu and on the toolbar. Of course, you first need to create and populate the method with content (say, for a menu item), and then in the Object Inspector, specify it for the toolbar button.
A more complex case is when within such a method you need to figure out who actually caused it. If potential candidates have a different object type (as in the previous paragraph - a button and a menu item), then it is the object type that can be used as a criterion:
If Sender is TMenuItem then ShowMessage('Menu item selected');
If all the objects that separate the same event handler belong to the same class, then you have to resort to additional tricks. A typical technique is to use the Tag property, which all components have, and, quite likely, this is what was intended:
const colors: array[0..7] of TColor = clWhite,clRed,clBlue,clYellow,clAqua,clGreen, clMaroon,clBlack); procedure TForml.CheckBoxClick(Sender: TObject); begin with TCheckBox(Sender) do if Checked then Color: = Colors[Tag] else Color: = clBtnFace; end;
Suppose the form has multiple radio buttons. To make each of them color when clicked, you need to set the Tag property to values from 0 to 7 in the Object inspector and associate the onclick event with the CheckBoxClick method for each. This single method will do the job for all radio buttons.