I need to assign event property at runtime using RTTI. But I can't create TValue
with type of
TNotifyEvent
it is always void (__closure *)(class System::TObject *) __attribute__((fastcall))
.
I've created helper method which could assist in set event at runtime, but continuously getting EInvalidCast with message 'Invalid class typecast'.
void __fastcall setProperty(TObject * component, const String name, TValue value)
{
auto * context = getContext(); // TRttiContext *
auto * property = context->GetType(component->ClassType())->GetProperty(name);
volatile String valueType = AnsiString(value.TypeInfo->Name); // "void (__closure *)(class System::TObject *) __attribute__((fastcall))"
volatile String propertyType = property->PropertyType->Name; // "TNotifyEvent"
volatile String oldType = AnsiString(property->GetValue(component).TypeInfo->Name); // "TNotifyEvent"
property->SetValue(component, value); // Raising exception
}
Calling side: btn is TButton *
auto value = TValue::From<TNotifyEvent>(&closeBtnClick);
setProperty(btn, "OnClick", value);
Also tried to use TMethod based on this question, but same exception raised: context is TRttiContext *
auto * method = context->GetType(__classid(TStandardPanel))->GetMethod("closeBtnClick");
TMethod * handler = new TMethod();
handler->Code = method->CodeAddress;
handler->Data = this;
auto value = TValue::From(TNotifyEvent(handler));
setProperty(btn, "OnClick", value);
But when I assign it directly it works:
btn->OnClick = &closeBtnClick;
Also it works if get TValue from other property and reassign to current.
TNotifyEvent
is a typedef
in C++:
typedef void __fastcall (__closure *TNotifyEvent)(System::TObject* Sender);
A typedef
is NOT a distinct type with its own RTTI. It is just an alias. Which is why you see the type of TNotifyEvent
being reported as void (__closure *)(class System::TObject *) __attribute__((fastcall))
, because that is the actual type as C++ sees it.
This is different than Delphi, where TNotifyEvent
is a distinct type with its own RTTI.
Your TMethod
attempt is wrong. Get rid of new
, you are type-casting the TMethod*
pointer wrong. You would need something more like this instead:
TMethod handler;
handler.Code = method->CodeAddress;
handler.Data = this;
auto value = TValue::From(reinterpret_cast<TNotifyEvent&>(handler));
Or:
auto value = TValue::From(*reinterpret_cast<TNotifyEvent*>(&handler));
Or:
TNotifyEvent handler;
TMethod &m = reinterpret_cast<TMethod&>(handler);
m.Code = method->CodeAddress;
m.Data = this;
auto value = TValue::From(handler);
But, in any of those cases, you are always going to get the TValue
type as void (__closure *)(class System::TObject *) __attribute__((fastcall))
since that is the real type of TNotifyEvent
on the C++ side.
So, to do what you are attempting, you need the Delphi RTTI for TNotifyEvent
. You can get that from TRttiContext::FindType()
, and then pass it to TValue::Make()
, eg:
PTypeInfo GetTNotifyEventTypeInfo()
{
auto * context = getContext();
auto * type = context->FindType(_D("System.Classes.TNotifyEvent"));
return type->Handle;
}
...
auto handler = closeBtnClick;
TValue value;
TValue::Make(&handler, GetTNotifyEventTypeInfo(), value);
setProperty(btn, _D("OnClick"), value);
However, many other event types exist than just TNotifyEvent
, so this is not a very flexible approach.
A better solution is to simply query the TypeInfo from the actual property that you are assigning to, which will always have the correct RTTI (otherwise DFM streaming would not work, etc), eg:
PTypeInfo GetPropertyTypeInfo(TObject * component, const String name)
{
auto * context = getContext();
auto * type = context->GetType(component->ClassType());
auto * property = type->GetProperty(name);
return property->PropertyType->Handle;
}
...
auto handler = closeBtnClick;
TValue value;
TValue::Make(&handler, GetPropertyTypeInfo(btn, _D("OnClick")), value);
setProperty(btn, _D("OnClick"), value);