Mega Code Archive

 
Categories / Delphi / OOP
 

Delegation, Events and Methods

Title: Delegation, Events and Methods Question: How do you delegate actions and operations in Delphi ? How do you use a stand-alone procedure as an event handler ? Answer: The basic types of reuse mechanisms are (see Gamma, Helm, Johnson and Vlissides, "Design Patterns", Addision-Wesley, 1995) Inheritance : reuse by subclassing, lets you define the implementation of one class in terms of another's Aggregation (or Composition): reuse by assembling or composing objects to get more complex functionality Delegation : reuse by connecting methods : a object that receives a request, delegates operations to another object (special kind of composition, two or more objects are involved to produce a behavior) The difference between inheritance and delegation is the same as between to "be" and to "have", for instance a rectangular windows-class "is" a subclass of a general window, or a window can "have" access to a class that knows how to draw a rectangular window. Delegation is used for example in the "State" and "Strategy" design patterns, which change the behavior of an object by changing the objects to which it delegates requests. In delphi, inheritance is a feature of the language, and its obvious how to compose objects. How does delegation work in delphi ? Delegation within a class is done by defining objects for a state or a strategy (see "State" and "Stategy" design pattern). Delegation between different classes is done by method pointers, variables that point to methods. I want to show this by an example : Let us suppose that you want to display the progress of a calculation in a progress-form. To do this, you can use messages or method pointers. Using messages, you can send messages with SendMessage(ProgressForm.Handle,WM_ProgressMsg,Progress,0); The progress-form can receive this message and respond to it with WndProc or own procedures like procedure WMProgress(var msg : TMessage); message WM_Progress; This is the "windows-way". The "delphi-way" is to use method pointers : Delphi uses them to manage and to hide the processing of messages : if a window-message like WM_Activate is received, the corresponding event OnActivate is triggered. An event encapsulates the response for a certain message, and is implemented by a method pointer. Instead of using SendMessage, we can define a "ProgressMessage" event with a method pointer : TProgressMsg = procedure(Msg : string;Progress : integer) of object; constructor TProgressForm.Create(LCID : integer;CompressClass : TCompressClass); begin inherited create(nil); fCompressClass:=CompressClass; fCompressClass.ProgressMsg:=ProgressMsg; ... end; Now we can delegate the process of displaying the progress to the progress-form : procedure TCompressClass.SendProgressMsg(Msg: string;Progress: integer); begin if Assigned(fProgressMsg) then fProgressMsg(Msg,Progress); end; The calling class knows, *when* the action is triggered, the class that rents the method knows exactly *what* to do in this case. procedure TProgressForm.ProgressMsg(Msg : string;Progress : integer); var s : string; k : integer; begin StepLabel.Caption:=Format(sProgress+' %s',[IntToStr(Progress)])+'%'; StepLabel.Refresh; end; ... Delegating or Event handling is done with Method Pointers. They allow you to change and to extend an object's behavior assigning new methods to the pointers. With method pointers, actions can be delegated to other classes. (- Events page of the Object-Inspector) Properties allow to change an object's state the properties. (- Properties page of the Object-Inspector) Normal properties encapsulate elementary blocks of states (access to object-data), "event properties" encapsulate elementary blocks of behavior (access to message-handling). All method variables, including the event properties, are defined in the unit System (former in SysUtils) as follows : TMethod = record Code, Data: Pointer; end; If you know that, you can do tricks like accessing methods by the method-name with (see Artikel 2644) Method.Code := Class.MethodAddress(MethodName); or using a Stand-alone procedure as an event handler Normally an event handler for the OnClick event would look like this : procedure TForm1.ClassProc(Sender: TObject); begin ... end; Because a Stand-alone procedure has no relation to a certain class, we have to add an additional Data parameter of type pointer (for methods, the first parameter is always Self (passed in EAX), and the first parameter explicitly declared is in fact the second parameter (passed in EDX)): procedure StandAloneProc(Data: Pointer; Sender: TObject); begin ... end; procedure TForm1.FormCreate(Sender: TObject); var Event: TNotifyEvent; begin TMethod(Event).Code := @StandAloneProc; TMethod(Event).Data := nil; // or Button1 Button1.OnClick := Event; end;