views:

428

answers:

2

I've recently began to upgrade my RAD Studio 2007 project to RAD Studio 2009. One thing I noticed is when seemingly simple code all of a sudden failed to compile.

Example Code:

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 );

This all use to compile, but with 2009 its failing. Looking at the inheritance chain for 2007 TBitBtn used to derive from TButton. Therefore, events that are expected on any button control (i.e. OnClick) were shared by the TButton class. Therefore, I was able to treat my TBitBtn class as a TButton.

2007 inheritance chain:

  • TBitBtn : TButton

2009 inheritance chain:

  • TBitBtn : TCustomButton
  • TButton : TCustomButton

In 2009, both TButton and TBitButton derive from TCustomButton, which would be fine I suppose if the button like attributes were held there. If this were the case, I could just change the code to deal with a TCustomButton instead. Unfortunately, TCustomButton does not hold things like OnClick. Therefore, I can no longer treat a TBitBtn like a TButton. Both of these classes, now have their own separate button like attributes (i.e. they both have their own OnClick event declared). I mean, at least provide an interface or something, like IButton that both TButton and TBitBtn implement.

It seems that these types of seemingly innocent changes are the ones that can wreak unnecessary havoc. This seems odd and am wondering if anyone knows why CodeGear (or any Framework author for that matter) would do this type of thing?

More importantly, given this fragmented inheritance, is there and elegant solution to treat a TBitBtn like a TButton?

+4  A: 

TButton and TBitBtn do still continue to share a common OnClick event, as it is implemented all the way down at the TControl level to begin with, and always has been. TButton was merely promoting the protected TControl::OnClick event to published, which TBitBtn would then inherit.

In D2009, TCustomButton, like other TCustom... classes, does not promote protected members from base classes to published. TButton and TBitBtn promote the protected TControl::OnClick event to published individually. But the event itself still exists at the TControl level.

Since it is protected at the TControl level, you can use an accessor class to reach it, ie:

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
    }
};

Or, for any general TControl pointer:

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
    }
};

A more elegant solution would be to use RTTI instead, which would also allow you to handle other types of objects, such as TSpeedButton, which have their own OnClick event, ie:

#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
    }
};

Or even:

#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
    }
};
Remy Lebeau - TeamB
Great information! What's the difference between the last two RTTI examples?
Scott Saad
The first operates on TObject pointers, and thus applies to all VCL object types regardless of their hierarchy, whereas the second operates only on TControl and descendants.
Remy Lebeau - TeamB
+1  A: 

If this was Delphi, I would suggest the TCustomButton class with the is and as operators:

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

C++ is simply too long ago

btw, didn't the VCL sometime include Actions to provide a single interface between buttons, menus, etc and invoked code?

devio
Would be interested in these Actions you reference. Will have to look that one up.
Scott Saad
In C++, I think one would probably check NULL != dynamic_cast<TButton>(SrcButton) to determine if SrcButton was actually a TButton.
Scott Saad
Actions tutorial http://www.blong.com/Conferences/BorCon2003/Actions/6102.htm
devio