ComboBox: Handling item reselection

What is the proper way of getting notified when the user selects the same item in a ComboBox that is currently selected?

What I tried so far with no success:
Overriding comboBoxChanged or valueChanged or handleAsyncUpdate in a subclassed ComboBox. All of those methods get only called if the value actually changed.

What I tried with partial success:
Passing a PopupMenu::CustomCallback that handles menuItemTriggered() to every PopupMenu::Item in the ComboBox. Problem here is that the callback in fact is called every time an item is selected but it has no knowledge of the actual item that was selected. Now asking the ComboBox which item is currently selected will still return the old value because it will only get updated after this callback.

From the description of menuItemTriggered() I would have assumed that always returning true from it will always force ComboBox::comboBoxChanged() to be called but that turned out to be a misassumption (Probably because another check for change is run after exitModalState):

true if the itemID should be sent to the exitModalState method, or false if it should send 0, indicating no further action should be taken

Now I guess I could make the PopupMenu::CustomCallback::menuItemTriggered() smart enough to know which item was selected if the callback is called but that would IMO imply having a PopupMenu::CustomCallback for every item in the ComboBox which seems overly complicated to me and I fear I’m missing the easy way.

Any suggestions on how to tackle this?

1 Like

I came up against this same problem today, when making a menu for plug-in presets.

Selecting a preset from the menu the first time would load the new parameter values just fine, but then selecting the same preset again would do nothing. This is a problem when a user modifies a parameter value in between! You want them to be able to revert to the stored preset values by selecting it again – but in its default behavior, ComboBox doesn’t do anything if a selection is the same as the previous one.

I looked into making the ComboBox an AudioProcessorValueTreeState::Listener, so it would be updated of changed parameter values, and thereby clear/reset its preset selection when that happened. However, for several reasons that I won’t delve into for now, that seemed like a bad idea.

In the end I figured out a stupid hack, which I’ll share here below. But I would appreciate any feedback from folks wiser in these matters…

In the Editor constructor, after addAndMakeVisible(presetMenu) and whatever else you might do there, add this:

presetMenu.onChange = [this]{
	if (presetMenu.getSelectedItemIndex() >= 0)
	{
		presetMenu.setTextWhenNothingSelected(presetMenu.getText());
		presetMenu.setSelectedId(0);
	}
};

Now whenever you make a selection from the ComboBox, it immediately resets itself to the “nothingSelected” state (i.e. index 0), but it changes the content of the “nothingSelected” text so that it looks like your selection is still made.

2 Likes

I have the same problem. I would like to be able to reselect the same item, and have it be processed as if it was selected for the first time. This hack doesn’t seem to work any longer…

It sure would be nice to add a method to ComboBox so you could specify this behavior, such as setCanReselectSameItem(bool state);

Just checked with a fresh JUCE 6 build, and it’s still working here. What part of it isn’t working for you?

hmm… I must be doing something wrong. Let me look into it again…

OK, when I use this, after making my selection, the code is indeed processed, but the item text is grayed out and the item is not checked in the popup menu.

I assume the greyed out text is just a LookAndFeel issue which draws the textWhenNothingSelected in a different colour, but when I open the popup menu, nothing is checked as being selected. Maybe that’s a trade-off you decided was OK?

For example, I simply process my function first, then do what you say:


    guiScaleCB.onChange = [this]{
        if (guiScaleCB.getSelectedItemIndex() >= 0)
        {
            setGUIScaleFactor(guiScaleCB.getSelectedItemIndex());

            guiScaleCB.setTextWhenNothingSelected(guiScaleCB.getText());
            guiScaleCB.setSelectedId(0);
        }
    };

This is after selecting 150%:
Untitled-1

On another topic, are we “allowed” to modify JUCE source and publish an app with it?

I mean, fixing this issue is a one line tweak. Just comment out the test for inequality:

void ComboBox::setSelectedId (const int newItemId, const NotificationType notification)
{
    auto* item = getItemForId (newItemId);
    auto newItemText = item != nullptr ? item->text : String();

    //if (lastCurrentId != newItemId || label->getText() != newItemText)
    {
        label->setText (newItemText, dontSendNotification);
        lastCurrentId = newItemId;
        currentId = newItemId;

        repaint();  // for the benefit of the 'none selected' text

        sendChange (notification);
    }
}

Of course, you have to remember to modify this again every time a new JUCE version is released, but it’s better than hacks…

You accomplish that with a fork, and that is absolutely leagl.

btw. another way to deal with that issue is to add your own lambdas to the PopupMenu of the ComboBox.

if (auto* menu = combo.getRootMenu())
    for (int i=0; i < presetNames.size(); ++i)
        menu->addItem (presetNames [i], [processor, i] { processor.setPreset (i); });

…untested…

Correct, in the case I was using this for, I didn’t care about that trade-off.

Yes, the greyed out text can be avoided by changing the LookAndFeel method, I overrode the drawComboBoxTextWhenNothingSelected to avoid that.

Drawing the checkmark is also handled in a LookAndFeel method (drawPopupMenuItem), but when you call setSelectedId(0), that means the ComboBox no longer “knows” which item was selected… so it has no way to pass that on to the LookAndFeel.

Please vote!

1 Like

I like this idea, and just tried implementing it, but it’s not working right off the bat. It does allow for the same item to be reselected, so it’s good on that front.

However, the ComboBox now seems unable to properly keep track of the items in the PopupMenu. At first, no checkmark is shown in the list, and then as soon as I select an item, a checkmark appears next to every item in the list. Also, the ComboBox’s text display (its Label) only shows the name of the first item, no matter which item is selected.

Note that the ComboBox::addItem method includes a newItemId argument, and that gets passed to the PopupMenu via PopupMenu::addItem [4/6]. But here, where you’re using PopupMenu::addItem [2/6] instead, it doesn’t take an Id argument, and I’m not clear yet how to work around that. (Unfortunately there’s not a version of PopupMenu::addItem that takes both an Id argument and a std::function argument.)