在 C++Builder 中将 OnDrawItem 动态分配给 TMenuItem

Assigning OnDrawItem to TMenuItem dynamically in C++Builder

TMenuItem *mi = new TMenuItem(this);

mi->OnDrawItem = &miThemesDrawItem;

导致错误:

[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)'

函数声明没问题,事实上我可以在设计时将其分配给设计时可用的菜单项。

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

我找到了解决方法。我在设计时在分隔项 N1 上分配了这个处理程序,然后在 运行 时我只是做 mi->OnDrawItem = N1->OnDrawItem,它工作正常,因为 OnDrawItem 没有得到需要菜单分隔符,但我不喜欢这个!

我错过了什么,如何分配这个处理程序?

Function declaration is okay

其实不然。 非常接近注意错误消息告诉您的内容:

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

那么,为什么 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.hppTMenuDrawItemEvent 实际 声明相匹配:

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参数是not声明为const!

您需要声明 miThemesDrawItem() 以具有 TMenuDrawItemEvent 声明方式完全相同的签名,例如:

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

这本身并不能保证 miThemesDrawItem()TMenuItem::OnDrawItem 事件 100% 兼容。虽然 IDE 确实在用户尝试将其分配给 design-time 处的事件时验证事件处理程序的类型,但 IDE 是一个 宽松在这个问题上。 IDE 在执行验证时依赖于 RTTI,而 RTTI 是基于 Delphi 信息,而不是 C++ 信息。 Const-correctness 在 Delphi 中的工作方式与在 C++ 中略有不同。因此,有时 IDE 确实允许 less-than-compatible 处理程序通过,特别是为了向后兼容(即 [=17 的 TRect 参数=] 并不总是 const).

验证后,IDE仅将事件处理程序的名称存储到DFM中,但直到[=90]才将实际功能分配给事件=](因为它的内存地址在 design-time 处未知)。当 DFM 在 run-time 处流入时,将函数分配给事件时不会再次验证函数的类型,盲目取用 as-is。因此,即使在 run-time,如果处理程序不是 100% 与事件兼容,可能 会出现微妙的问题。

另一方面,编译器要求函数类型与分配给它的函数指针 100% 兼容。没有任何差异的余地。因此,在这种情况下,参数上 const 限定符的差异很大 no-no。这就是为什么编译器不允许您将 miThemesDrawItem() 分配给代码中的 TMenuItem::OnDrawItem 事件,直到您修复 miThemesDrawItem() 的签名以匹配 TMenuDrawItemEvent 正是.