【问题标题】:Assigning OnDrawItem to TMenuItem dynamically in C++Builder在 C++Builder 中动态地将 OnDrawItem 分配给 TMenuItem
【发布时间】:2022-01-27 19:38:52
【问题描述】:
TMenuItem *mi = new TMenuItem(this);

mi->OnDrawItem = &miThemesDrawItem;

导致错误:

[bcc64 错误] _TForm1.cpp(280): 分配给 'Vcl::Menus::TMenuDrawItemEvent' (aka 'void ((__closure *))(System::TObject *, Vcl::Graphics::TCanvas * , const System::Types::TRect &, bool) __attribute__((fastcall))') 来自不兼容类型 '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 没有被称为菜单分隔符,但我没有不喜欢这样!

我缺少什么,如何分配这个处理程序?

【问题讨论】:

    标签: c++builder vcl


    【解决方案1】:

    函数声明没问题

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

    从不兼容的类型分配给 '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.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 参数声明为 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 确实允许不兼容的处理程序通过,尤其是为了向后兼容(即TMenuDrawItemEventTRect 参数并不总是const)。

    验证后,IDE 仅将事件处理程序的 名称 存储到 DFM 中,但实际函数直到运行时才分配给事件(因为它的内存地址在设计时)。当 DFM 在运行时流式传输时,将函数分配给事件时不会再次验证函数的类型,它被盲目地按原样获取。因此,即使在运行时,如果处理程序与事件不是 100% 兼容,可能也会出现微妙的问题。

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

    【讨论】:

    • 谢谢!有趣的是,这个函数是由 IDE 生成的,我双击项目的处理程序属性之一来生成它。我认为函数声明不会有任何问题。
    • 事件处理程序的自动生成基于 Delphi RTTI,而 IIRC 在某些版本的 IDE 中,该过程存在细微问题,尤其是在 64 位下,其中 ABI 可能与 32 位下略有不同当涉及在特定尺寸阈值以下的结构中传递时。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2022-06-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-01-14
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多