函数声明没问题
其实不然。 非常密切地注意错误消息告诉您的内容:
从不兼容的类型分配给 'Vcl::Menus::TMenuDrawItemEvent' ...'
那么,为什么miThemesDrawItem() 不兼容?因为它的类型与 TMenuDrawItemEvent 所期望的不同!
让我们先看看TMenuDrawItemEvent。错误消息说它的类型是:
void ((__closure *))(System::TObject *, Vcl::Graphics::TCanvas *, const System::Types::TRect &, bool) __attribute__((fastcall))
我们现在可以忽略__attribute__,因此类型是:
void (__closure *)(System::TObject *, Vcl::Graphics::TCanvas *, const System::Types::TRect &, bool)
这与Vcl.Menus.hpp 中TMenuDrawItemEvent 的实际 声明相匹配:
typedef void __fastcall (__closure *TMenuDrawItemEvent)(System::TObject* Sender, Vcl::Graphics::TCanvas* ACanvas, const System::Types::TRect &ARect, bool Selected);
现在,让我们看看您的miThemesDrawItem()。错误消息说它的类型是:
void (__closure *)(System::TObject *, Vcl::Graphics::TCanvas *, System::Types::TRect &, bool)
这与您的 实际 声明相符:
void __fastcall miThemesDrawItem(TObject *Sender, TCanvas *ACanvas, TRect &ARect, bool Selected);
注意到TMenuDrawItemEvent 的声明类型与miThemesDrawItem() 的声明类型之间有什么区别吗? miThemesDrawItem() 中的 TRect 参数不声明为 const!
您需要声明 miThemesDrawItem() 以具有与声明 TMenuDrawItemEvent 的方式完全相同的签名,例如:
void __fastcall miThemesDrawItem(TObject* Sender, TCanvas* ACanvas, const TRect &ARect, bool Selected);
事实上,我可以在设计时将其分配给在设计时可用的菜单项
这本身并不能保证miThemesDrawItem() 与TMenuItem::OnDrawItem 事件100% 兼容。尽管当用户在设计时尝试将事件处理程序分配给事件时 IDE 会验证事件处理程序的类型,但 IDE 在这件事上是一点 宽容的。 IDE 在执行验证时依赖于 RTTI,而 RTTI 是基于 Delphi 信息,而不是 C++ 信息。常量正确性在 Delphi 中的工作方式与在 C++ 中的工作方式略有不同。因此,有时IDE 确实允许不兼容的处理程序通过,尤其是为了向后兼容(即TMenuDrawItemEvent 的TRect 参数并不总是const)。
验证后,IDE 仅将事件处理程序的 名称 存储到 DFM 中,但实际函数直到运行时才分配给事件(因为它的内存地址在设计时)。当 DFM 在运行时流式传输时,将函数分配给事件时不会再次验证函数的类型,它被盲目地按原样获取。因此,即使在运行时,如果处理程序与事件不是 100% 兼容,可能也会出现微妙的问题。
另一方面,编译器要求函数的类型与分配给它的函数指针 100% 兼容。没有任何差异的余地。因此,在这种情况下,参数上const 限定符的差异是一个很大的禁忌。这就是为什么编译器不会让您将miThemesDrawItem() 分配给代码中的TMenuItem::OnDrawItem 事件,直到您修复miThemesDrawItem() 的签名以匹配TMenuDrawItemEvent 的类型完全。