I have a class that has a few event properties, and another class that contains the event handlers. At compile-time I don't know the structure of either class, at run-time I only get to know the match between event property and event handler using their names. Using RTTI, I'd like to assign the event handlers to the respective event properties, how can I do so?
I currently have something like this:
type
TMyEvent = reference to procedure(const AInput: TArray<string>; out AOutput: TArray<string>);
TMyBeforeEvent = reference to procedure(const AInput: TArray<string>; out AOutput: TArray<string>; out ACanContinue: boolean);
TMyClass = class
private
FOnBeforeEvent: TMyBeforeEvent;
FOnEvent: TMyEvent;
public
property OnBeforeEvent: TMyBeforeEvent read FOnBeforeEvent write FOnBeforeEvent;
property OnEvent: TMyEvent read FOnEvent write FOnEvent;
end;
TMyEventHandler = class
public
procedure DoBeforeEvent(const AInput: TArray<string>; out AOutput: TArray<string>; out ACanContinue: boolean);
procedure DoEvent(const AInput: TArray<string>; out AOutput: TArray<string>);
end;
procedure AssignEvent;
implementation
uses
Vcl.Dialogs, System.RTTI;
{ TMyEventHandler }
procedure TMyEventHandler.DoBeforeEvent(const AInput: TArray<string>;
out AOutput: TArray<string>; out ACanContinue: boolean);
begin
// do something...
end;
procedure TMyEventHandler.DoEvent(const AInput: TArray<string>;
out AOutput: TArray<string>);
begin
// do something...
end;
procedure AssignEvent;
var
LObj: TMyClass;
LEventHandlerObj: TMyEventHandler;
LContextObj, LContextHandler: TRttiContext;
LTypeObj, LTypeHandler: TRttiType;
LEventProp: TRttiProperty;
LMethod: TRttiMethod;
LNewEvent: TValue;
begin
LObj := TMyClass.Create;
LEventHandlerObj := TMyEventHandler.Create;
LContextObj := TRttiContext.Create;
LTypeObj := LContextObj.GetType(LObj.ClassType);
LEventProp := LTypeObj.GetProperty('OnBeforeEvent');
LContextHandler := TRttiContext.Create;
LTypeHandler := LContextHandler.GetType(LEventHandlerObj.ClassType);
LMethod := LTypeHandler.GetMethod('DoBeforeEvent');
LEventProp.SetValue(LObj, LNewEvent {--> what value should LNewEvent have?});
end;
Good question!
I believe I have found a sound approach.
To illustrate it, create a new VCL application with a form (Form1
) and a single button (Button1
) on it. Give the form an OnClick
handler:
procedure TForm1.FormClick(Sender: TObject);
begin
ShowMessage(Self.Caption);
end;
We will attempt to assign this handler to the button's OnClick
property using only RTTI and property and method names (as strings).
The key thing to remember is that a method pointer is a pair (code pointer, object pointer), specifically, a TMethod
record:
procedure TForm1.FormCreate(Sender: TObject);
var
C: TRttiContext;
b, f: TRttiType;
m: TRttiMethod;
bp: TRttiProperty;
handler: TMethod;
begin
C := TRttiContext.Create;
f := C.GetType(TForm1);
m := f.GetMethod('FormClick');
b := C.GetType(TButton);
bp := b.GetProperty('OnClick');
handler.Code := m.CodeAddress;
handler.Data := Form1;
bp.SetValue(Button1, TValue.From<TNotifyEvent>(TNotifyEvent(handler)));
end;