Что случилось с цепочкой наследования TBitBtn и TButton?

Я недавно начал обновлять свой проект RAD Studio 2007 до RAD Studio 2009. Одна вещь, которую я заметил, это то, что казалось бы простой код неожиданно не скомпилировался.

Пример кода:

class CButtonPopupMenu
{
    // Snip

public:
    void Init( TButton* SrcButton )
    {
        SrcButton->OnClick = OnButtonClick;
    }

private:
    void __fastcall OnButtonClick( TObject* Sender )
    {
        // Do some button click stuff
    }
};

// Snip

TButton button = new TButton( this );
TBitBtn bitBtn = new TBitBtn( this );
CButtonPopupMenu popupButton = new CButtonPopupMenu( button );
CButtonPopupMenu popupBitBtn = new CButtonPopupMenu( bitBtn );

Это все используется для компиляции, но с 2009 года это не удается. Глядя на цепочку наследования на 2007 год TBitBtn используется для получения от TButton, Таким образом, события, которые ожидаются на любом элементе управления кнопки (например, OnClick) были разделены TButton учебный класс. Поэтому я смог лечить TBitBtn класс как TButton,

Цепочка наследования 2007 года:

  • TBitBtn: TButton

2009 цепочка наследования:

  • TBitBtn: TCustomButton
  • TButton: TCustomButton

В 2009 году и TButton, и TBitButton являются производными от TCustomButton, что было бы неплохо, если бы там были атрибуты, подобные кнопкам. Если бы это было так, я мог бы просто изменить код, чтобы иметь дело с TCustomButton. К сожалению, TCustomButton не поддерживает такие вещи, как OnClick. Поэтому я больше не могу относиться к TBitBtn как к TButton. Оба этих класса теперь имеют свои собственные отдельные атрибуты, подобные кнопкам (т.е. у них обоих объявлено собственное событие OnClick). Я имею в виду, по крайней мере, предоставить интерфейс или что-то подобное, как IButton, который реализуют и TButton, и TBitBtn.

Кажется, что эти типы, казалось бы, невинных изменений - это те, которые могут нанести ненужный ущерб. Это кажется странным, и я задаюсь вопросом, знает ли кто-нибудь, почему CodeGear (или какой-либо автор Framework в этом отношении) будет делать такие вещи?

Что еще более важно, учитывая это фрагментированное наследование, существует ли и элегантное решение для лечения TBitBtn как TButton?

2 ответа

Решение

TButton и TBitBtn по-прежнему продолжают использовать общее событие OnClick, поскольку оно изначально реализовано на уровне TControl и всегда имело место. TButton просто рекламировал защищенное событие TControl::OnClick для публикации, которую TBitBtn затем наследовал.

В D2009 TCustomButton, как и другие классы TCustom..., не продвигает защищенных членов из базовых классов в опубликованные. TButton и TBitBtn рекламируют защищенное событие TControl::OnClick, которое публикуется индивидуально. Но само событие все еще существует на уровне TControl.

Так как он защищен на уровне TControl, вы можете использовать класс доступа, чтобы достичь его, то есть:

class TCustomButtonAccess
{
public:
    __property OnClick;
};

class CButtonPopupMenu
{
    // Snip

public:
    void Init( TCustomButton* SrcButton )
    {
        ((TCustomButtonAccess*)SrcButton)->OnClick = OnButtonClick;
    }

private:
    void __fastcall OnButtonClick( TObject* Sender )
    {
        // Do some button click stuff
    }
};

Или для любого общего указателя TControl:

class TControlAccess
{
public:
    __property OnClick;
};

class CControlPopupMenu
{
    // Snip

public:
    void Init( TControl* SrcControl )
    {
        ((TControlAccess*)SrcControl)->OnClick = OnControlClick;
    }

private:
    void __fastcall OnControlClick( TObject* Sender )
    {
        // Do some click stuff
    }
};

Более элегантным решением было бы вместо этого использовать RTTI, что также позволило бы вам обрабатывать другие типы объектов, такие как TSpeedButton, которые имеют свое собственное событие OnClick, а именно:

#include <TypInfo.hpp>

class TControlAccess
{
public:
    __property OnClick;
};

class CControlPopupMenu
{
    // Snip

public:
    void Init( TControl* SrcControl )
    {
        TMethod m;
        m.Code = &OnControlClick;
        m.Data = this;
        SetMethodProp(SrcControl, "OnClick", m);
    }

private:
    void __fastcall OnControlClick( TObject* Sender )
    {
        // Do some click stuff
    }
};

Или даже:

#include <TypInfo.hpp>

class CObjectPopupMenu
{
    // Snip

public:
    void Init( TObject* SrcObject )
    {
        TMethod m;
        m.Code = &OnObjectClick;
        m.Data = this;
        SetMethodProp(SrcObject, "OnClick", m);
    }

private:
    void __fastcall OnObjectClick( TObject* Sender )
    {
        // Do some click stuff
    }
};

Если бы это был Delphi, я бы предложил класс TCustomButton с операторами is и as:

if (SrcButton is TButton) then
  (SrcButton as TButton).OnClick := OnButtonClick
else if (SrcButton is TBitButton)
  (SrcButton as TBitButton).OnClick := OnButtonClick;

C++ просто слишком давно

Кстати, VCL когда-нибудь включал в себя действия, чтобы обеспечить единый интерфейс между кнопками, меню и т. д. и вызванным кодом?

Другие вопросы по тегам