Search code examples
c++buildervcl

Assigning OnDrawItem to TMenuItem dynamically in C++Builder


TMenuItem *mi = new TMenuItem(this);

mi->OnDrawItem = &miThemesDrawItem;

results in error:

[bcc64 Error] _TForm1.cpp(280): assigning to 'Vcl::Menus::TMenuDrawItemEvent' (aka 'void ((__closure *))(System::TObject *, Vcl::Graphics::TCanvas *, const System::Types::TRect &, bool) __attribute__((fastcall))') from incompatible type 'void (__closure *)(System::TObject *, Vcl::Graphics::TCanvas *, System::Types::TRect &, bool)'

Function declaration is okay, in fact I can assign it at design-time to menu items that are available at design-time.

void __fastcall miThemesDrawItem(TObject *Sender, TCanvas *ACanvas, TRect &ARect, bool Selected);

I found a workaround. I assigned this handler at design-time on a separator item N1, and then at run-time I just do mi->OnDrawItem = N1->OnDrawItem, and it works fine since OnDrawItem does not get called for a menu separator, but I don't like this!

What am I missing, how to assign this handler?


Solution

  • Function declaration is okay

    Actually, it is not. Pay very close attention to what the error message is telling you:

    assigning to 'Vcl::Menus::TMenuDrawItemEvent' ... from incompatible type ...'

    So, why is miThemesDrawItem() incompatible? Because it is has a different type than what TMenuDrawItemEvent is expecting!

    Let's look at TMenuDrawItemEvent first. The error message says its type is:

    void ((__closure *))(System::TObject *, Vcl::Graphics::TCanvas *, const System::Types::TRect &, bool) __attribute__((fastcall))
    

    We can ignore the __attribute__ for now, thus the type is:

    void (__closure *)(System::TObject *, Vcl::Graphics::TCanvas *, const System::Types::TRect &, bool)
    

    That matches up with the actual declaration of TMenuDrawItemEvent in Vcl.Menus.hpp:

    typedef void __fastcall (__closure *TMenuDrawItemEvent)(System::TObject* Sender, Vcl::Graphics::TCanvas* ACanvas, const System::Types::TRect &ARect, bool Selected);
    

    Now, let's look at your miThemesDrawItem(). The error message says its type is:

    void (__closure *)(System::TObject *, Vcl::Graphics::TCanvas *, System::Types::TRect &, bool)
    

    That matches up with your actual declaration:

    void __fastcall miThemesDrawItem(TObject *Sender, TCanvas *ACanvas, TRect &ARect, bool Selected);
    

    Notice any differences between the declared type of TMenuDrawItemEvent vs the declared type of miThemesDrawItem()? The TRect parameter in miThemesDrawItem() is not declared as const!

    You need to declare miThemesDrawItem() to have the exact same signature as how TMenuDrawItemEvent is declared, eg:

    void __fastcall miThemesDrawItem(TObject* Sender, TCanvas* ACanvas, const TRect &ARect, bool Selected);
    

    in fact I can assign it at design-time to menu items that are available at design-time

    That by itself doesn't guarantee that miThemesDrawItem() is 100% compatible with the TMenuItem::OnDrawItem event. Although the IDE does validate an event handler's type when the user tries to assign it to an event at design-time, the IDE is a little lenient on this matter. The IDE is relying on RTTI when performing the validation, and the RTTI is based on Delphi information, not C++ information. Const-correctness works a little differently in Delphi than in C++. So, sometimes the IDE does allow less-than-compatible handlers through, especially for the sake of backwards compatibility (ie, the TRect parameter of TMenuDrawItemEvent wasn't always const).

    After validation, the IDE merely stores the name of the event handler into the DFM, but the actual function is not assigned to the event until run-time (since its memory address is not known at design-time). When the DFM is streamed in at run-time, the function's type will not be validated again when the function is assigned to the event, it is blindly taken as-is. So, even at run-time, there may be subtle issues if the handler is not 100% compatible with the event.

    The compiler, on the other hand, requires the type of a function to be 100% compatible with a function pointer that it is being assigned to. There is no room for any differences. So, in this case, a difference in const qualifiers on a parameter is a big no-no. That is why the compiler won't let you assign miThemesDrawItem() to the TMenuItem::OnDrawItem event in your code, until you fix the signature of miThemesDrawItem() to match the type of TMenuDrawItemEvent exactly.