nullptr returned when trying to cast tgui::ListBox to custom ListBox

Started by EngineerBread, 22 June 2024, 05:23:51


Hi community I hope you are doing well.
My problem is the following one: I have a custom ListBox type in order to add a "onRightClick" signal, here is the code:

class ImprovedListBox : public tgui::ListBox
    float getItemHeight();
    typedef std::shared_ptr<ImprovedListBox> Ptr; ///< Shared widget pointer
    typedef std::shared_ptr<const ImprovedListBox> ConstPtr; ///< Shared constant widget pointer

    // Signal for left-click events
    tgui::Signal onRightClick = { "rightClick" };

    // Factory function to create the custom list box
    static ImprovedListBox::Ptr create();
    static ImprovedListBox::Ptr copy(const tgui::ListBox::Ptr &);
    void rightMousePressed(tgui::Vector2f pos) override;

No problems so far, I have a tgui::ListBox added to my form using the tool gui-builder, the problem comes when trying to cast that previously added ListBox into my own type, here is the code:

auto vanillaList(gui->get<tgui::ListBox>("EntitiesList"));
auto entitiesList=vanillaList->cast<ImprovedListBox>();

I've tried everything, I tried std::dynamic_cast and std::static_cast but the problem persist, so I wanted to ask.
Which's the right way to do this?
What I'm trying to do is even correct?

Thanks and regards!! (^-^)/


QuoteWhat I'm trying to do is even correct?
Unfortunately it isn't. You can only cast to a derived class if the object was from that type in the first place. So if you create an ImprovedListBox, cast it to a base class such as ListBox or Widget to store it somewhere, then you can cast it back to ImprovedListBox later. If the ImprovedListBox was however never created, and the widget was created as a ListBox, then it can't suddenly become a ImprovedListBox.

If the derived class would only contain simple member functions they you could get it to work (even though it would be undefined behavior according to the c++ standard), but that is not the case here. The onRightClick member requires several bytes of extra storage, which weren't allocated when the ListView was constructed. And the rightMousePressed function requires an altered virtual function table for it to be called correctly. So even with undefined behavior, this cast is never going to work.

QuoteWhich's the right way to do this?

* Method 1

The gui builder can't create your custom widgets, it can only create normal ListBox widgets.
So the most obvious solution would be to create all such list boxes in c++ code instead of loading them from a form.

Maybe you can combine the two by still having a ListBox widget in the form, but looping through all widgets after loading it to replace them. You would remove each ListBox widget from the gui and add a new ImprovedListBox widget in the same spot at the same position and with the same size as the removed ListBox. Implementing something like this isn't trivial though, you have to e.g. be careful not to alter the list you are looping through.

* Method 2

Loading widgets from a file can however support custom widgets. So you can make it work by manually editing the form file with a text editor after the gui builder created it.

The form contains a line like "ListBox.ListBox1", where "ListBox1" is the name you gave the widget in the gui builder. You need to change it to "ImprovedListBox.ListBox1". Note that after doing this, the Gui Builder can no longer open the form again, it will complain that it doesn't recognize widgets of type ImprovedListBox.

The loadWidgetsFromFile function in your code also won't know how to create such type out of the box. This can however be solved by adding the following line at the beginning of your program:
Code (cpp) Select
tgui::WidgetFactory::setConstructFunction("ImprovedListBox", std::make_shared<ImprovedListBox>);

Now when loading the form file containing an ImprovedListBox, you can get it directly with gui->get<tgui::ImprovedListBox>("EntitiesList")