comboboxChanged is not being called when i click on the same item

Does anybody know a way to solve this issue ? I am using a combobox to display my presets and after loading a preset, user can change many things but if the user does not want to keep it like that or reload the unmodified preset, he/she will try to go to combobox again and click on the same preset. At that moment the preset should be reloaded but I when i try to click on the same item, it does not call comboboxChange. I tried to do it by using mouse down event, resetting selected id,… nothing worked .

When the PopupMenu is dismissed, the comboBoxPopupMenuFinishedCallback function is called (defined in juce_ComboBox.cpp). This function calls ComboBox::setSelectedId, which is basically a no-op when the selected item ID hasn’t changed, so this is why no changes are being broadcasted.

As far as I can tell, your best bet is to override ComboBox::showPopup. This way you can define your own callback that reloads the preset regardless of whether the selected item ID actually changes, and register the callback with the PopupMenu when you show it via PopupMenu::showMenuAsync.

Thank you very much for your answer. :pray: I only need one more extra piece of advice :slight_smile: Let’s say I’ve overridden the showPopup()
Actually for this, I will need to keep all content of the showpopup + add my lines. It is a normal function, so if I override it, it won’t show the popup menu untill I create one for it. But then the content and some flags of base class wont be synchronized anymore.

//====================================================================
void ComboBox::showPopup()
{
if (! menuActive)
menuActive = true;

      auto menu = currentMenu;

if (menu.getNumItems() > 0)
{
    auto selectedId = getSelectedId();

    for (PopupMenu::MenuItemIterator iterator (menu, true); iterator.next();)
    {
        auto& item = iterator.getItem();

        if (item.itemID != 0)
            item.isTicked = (item.itemID == selectedId);
    }
}
else
{
    menu.addItem (1, noChoicesMessage, false, false);
}

auto& lf = getLookAndFeel();

menu.setLookAndFeel (&lf);
menu.showMenuAsync (lf.getOptionsForComboBoxPopupMenu (*this, *label),
ModalCallbackFunction::forComponent (comboBoxPopupMenuFinishedCallback, this));
         }

//====================================================================
( note : I put 4 spaces but preformating didn’t work. )

As you can see there are some flags like “menuActive” and some inaccessible contents like “noChoicesMessage”. What do you suggest me to do to override a normal function without changing its content?

Huh, that is a bit trickier than I expected!

It looks like the menuActive member is set to true in ComboBox::showPopupIfNotActive(), which is what gets called when the user clicks on the ComboBox. So, as long as you are not calling showPopup() from code outside the class, I think you can get away with ignoring it entirely and deleting those lines.

The noChoicesMessage member can be accessed using getTextWhenNoChoicesAvailable(), it defaults to “(no choices)”.

Similarly, you can access the currentMenu via getRootMenu().

One tricky bit is the label member that gets passed in when creating the PopupMenu::Options struct – it is inaccessible, but it appears to be used for determining the menu’s item height. The label height is actually set in the positionComboBoxText method of the active LookAndFeel object, so unless you’ve overridden that somewhere, it should be comboBox->getHeight() - 2. Feels pretty hacky, but I guess you could create a dummy Label, set its height, and pass it to getOptionsForComboBoxPopupMenu()? Surely there’s a better way, but this could be something to get started with.

Here’s a bit of quick and dirty code illustrating the idea:

struct CustomComboBox   : ComboBox
{
    CustomComboBox() = default;

    /** Called with the selected item's ID whenever an item selection occurs. */
    std::function<void (int)> onItemSelected;

private:
    /** Calling this function directly will cause things to break because we
        can't set the ComboBox::menuActive flag ourselves, so this method is private.
    */
    void showPopup() override
    {
        Label dummyLabel;
        dummyLabel.setSize (0, getHeight() - 2);

        if (auto* menu = getRootMenu())
        {
            if (menu->getNumItems() > 0)
            {
                const int selectedId = getSelectedId();
        
                for (PopupMenu::MenuItemIterator iterator (*menu, true); iterator.next();)
                {
                    auto& item = iterator.getItem();
        
                    if (item.itemID != 0)
                        item.isTicked = (item.itemID == selectedId);
                }
            }
            else
            {
                menu->addItem (1, getTextWhenNoChoicesAvailable(), false, false);
            }
        
            auto& lf = getLookAndFeel();
            menu->setLookAndFeel (&lf);
            jassert (onItemSelected); // You must assign a valid function object!

            menu->showMenuAsync (lf.getOptionsForComboBoxPopupMenu (*this, dummyLabel),
                                 ModalCallbackFunction::create (itemSelectedCallback, this));
        }
    }

    /** Called whenever one of the PopupMenu items is selected. */
    static void itemSelectedCallback (int itemId, CustomComboBox* comboBox)
    {
        if (comboBox != nullptr)
        {
            comboBox->hidePopup();

            if (itemId != 0)
            {
                comboBox->setSelectedId (itemId);

                if (comboBox->onItemSelected)
                    comboBox->onItemSelected (itemId);
            }
        }
    }
};
1 Like

I don’t know what to say but I will start with "Thank you very much! " :slight_smile: Hopefully this will solve the problem :pray: