Updating ComboBox via c++ code

Hi people. I am strugling to get my combobox to show a new selection by code.
When creting the cox, I use outputSelector.setSelectedId(1); which nicely shows the 1st item in the list when the program is “build”.

At certain point, I want the shown output to change via outputSelector…setSelectedId(newselection, , juce::dontSendNotification); after that I change my midioutput via midiOutput = juce::MidiOutput::openDevice(outputSelector.getSelectedId() - 1);

The weird thing is, that it opens the correct port and sends a message, but the combobox isn’t updated. Changing the Notification doesn’t help either.

After this routine is done, I press a key on my midi controller and notice that midiOutput has “changed back” to the actual visible one, whithout my changing the code.

Here is some of the code:

[code] {
// part of creation of the combobox
outputSelector.addItemList(midiOutputs, 1);
outputSelector.setSelectedId(1);
outputSelector.setEnabled(true);

juce::MidiOutput::openDevice(outputSelector.getText());

void sendMidiMessage(int outChannel, int cc0, int cc32, int pc, juce::String outPort)
{
int outID = midiOutputs.indexOf(outPort);
if (outID >= 0)
{

        outputSelector.setSelectedId(outID, juce::dontSendNotification);
        midiOutput = juce::MidiOutput::openDevice(outputSelector.getSelectedId() - 1);
    }
    if (midiOutput != nullptr)
    {
        juce::MidiMessage msg;
        if (cc0 >= 0)
            msg = juce::MidiMessage::controllerEvent(outChannel, 0, cc0);
        if (cc32 >= 0)
            msg = juce::MidiMessage::controllerEvent(outChannel, 32, cc32);
        if (pc >= 0)
            msg = juce::MidiMessage::programChange(outChannel, pc);
        midiOutput->sendMessageNow(msg);
        
    }
}

void handleIncomingMidiMessage(juce::MidiInput* source, const juce::MidiMessage& message) override
{
if (midiOutput != nullptr)
{
midiOutput->sendMessageNow(message);
}
}

}[/code]

You may want to try setSelectedItemIndex() instead. There’s a difference between the itemID and the itemIndex.

Hi stephenk, I already tried that. The only difference is that IF I set notification on, it triggers the onChange, which doesn’t happen with the setSelectedId(). Also tried the setText().

It seems I have no control at all. Even outputSelector.clear() with or without outputSelector.repaint() doesn’t clear the list. I thought maybe clear and rebuild with my outId as initial value would be an option. But no, that doesn’t work either.

@jules Any idea?

I think midiOutputs.indexOf(outPort) returns a 0-relative index. So the first item in midiOutputs would be index 0 (it returns -1 if not located).

The ComboBox indexes are 1-relative. If you try to set your ComboBox to index 0, it’s not going to work. As you stated, calling setSelectedID(1) selects the first item. If you try calling your function with the second port’s name, indexOf() will return 1, and set it back to the first port.

So you should try:

outputSelector.setSelectedId(outID + 1, juce::dontSendNotification);

Hi @stephenk. That isn’t the problem though. I tried outputSelector.setSelectedId(3, juce::dontSendNotification); So I hardcoded the OutID to 3 for testing, which should update the combobox. But it doesn’t. And like I said, it technically works as the message is send on the correct port. Only the combobox isn’t updated.

As far I understand outputSelector.clear() should clear the content of the combobox. For test purposes I tried that and even that isn’t working.

I think I will make a copy of the Combobox file and then alter the original to have the .nudge() in public instead of private and see if I can use that.

You really shouldn’t have to do that. ComboBox works. I do what you’re trying to do all the time. But I don’t see the problem in your code snippet.

Are you calling sendMidiMessage() on the Message Thread, or the Audio Thread?

@stephenk , what I shouldn’t have to do? Combobox itself works fine when selecting it from the combobox. What I need is the combobox to update when I select the new item from code, not the combobox. sendMidiMessage() works fine itself. It is called from another .h file. All the code posted is part of midihandling.h file. So the only problem is the combobox not showing the new selected item when doing it from code. Also any “graphic” change doesn’t work from code like .clear. I made outID a global variable, so I can use it to set the correct port in handleIncomingMidiMessage() and that works. It just doesn’t show the selected item in the box.

All I’m saying is that, in normal operation, the ComboBox can be updated with a new selection from code, and it works. You shouldn’t have to rewrite it. However, I am unable to see the problem from the code you’ve posted, maybe someone else can. Good luck.

Yeah can’t see anything wrong either. Thx for trying to help though.

I changed the code a bit and added a button for testing. That works if inside the function, but setting it from another function isn’t working. Maybe someone has an idea

{
#pragma once

#include <JuceHeader.h>
inline int outID = 0;


class MyMidiComponent : public juce::Component,
    public juce::MidiInputCallback
    
{
public:
    MyMidiComponent()
    {
         addAndMakeVisible(outputSelector);
        outputSelector.setTextWhenNoChoicesAvailable("No MIDI Outputs Enabled");
        outputSelector.onChange = [this] { midiOutput = juce::MidiOutput::openDevice(outputSelector.getSelectedId() - 1); };
        midiOutputs = juce::MidiOutput::getDevices();
        if (midiOutputs.size() > 0)
        {
            outputSelector.addItemList(midiOutputs, 1);
            outputSelector.setSelectedId(1);
            outputSelector.setEnabled(true);
        }
        else
        {
            outputSelector.setTextWhenNoChoicesAvailable("No MIDI Outputs Available");
            outputSelector.setEnabled(false);
        }
        juce::MidiOutput::openDevice(outputSelector.getText());
        outputSelector.setBounds(400, 10, 240, 20);

        myButton.setButtonText("Set");
        myButton.setBounds(360, 10, 30, 20);
        myButton.onClick = [this]() { outputSelector.setSelectedId(outID + 1, juce::dontSendNotification); };
        addAndMakeVisible(myButton);
    }

    void sendMidiMessage(int outChannel, int cc0, int cc32, int pc, juce::String outPort)
    {
        outID = 1; // midiOutputs.indexOf(outPort);
        if (outID >= 0)
        {
            outputSelector.setSelectedId(outID + 1, juce::dontSendNotification);
            outputSelector.repaint();
            midiOutput = juce::MidiOutput::openDevice(outputSelector.getSelectedId() - 1);
        }
        if (midiOutput != nullptr)
        {
            juce::MidiMessage msg;
            if (cc0 >= 0)
                msg = juce::MidiMessage::controllerEvent(outChannel, 0, cc0);
            if (cc32 >= 0)
                msg = juce::MidiMessage::controllerEvent(outChannel, 32, cc32);
            if (pc >= 0)
                msg = juce::MidiMessage::programChange(outChannel, pc);
            midiOutput->sendMessageNow(msg);
        }
    }

    ~MyMidiComponent()
    {
        if (midiInput != nullptr)
        {
            midiInput->stop();
            midiInput.reset();
            deviceManager.removeMidiInputDeviceCallback(juce::MidiInput::getAvailableDevices()[inputSelector.getSelectedItemIndex()].identifier, this);
        }
        if (midiOutput != nullptr)
            midiOutput.reset();
    }

    void paint(juce::Graphics& g) override
    {
    }

    void resized() override
    {
    }

    
    // MIDI input callback

    void handleIncomingMidiMessage(juce::MidiInput* source, const juce::MidiMessage& message) override
    {
        midiOutput = juce::MidiOutput::openDevice(outID);
        if (midiOutput != nullptr)
        {
            midiOutput->sendMessageNow(message);
        }
    }

    
private:
    juce::AudioDeviceManager deviceManager;
    int lastInputIndex = 0;
    juce::ComboBox inputSelector, outputSelector;
    std::unique_ptr<juce::MidiInput> midiInput;
    std::unique_ptr<juce::MidiOutput> midiOutput;
    juce::StringArray midiOutputs;
    juce::StringArray midiInputs;
    juce::TextButton myButton;
    

    /** Starts listening to a MIDI input device, enabling it if necessary. */
    void setMidiInput(int index)
    {
        auto list = juce::MidiInput::getAvailableDevices();

        deviceManager.removeMidiInputDeviceCallback(list[lastInputIndex].identifier, this);

        auto newInput = list[index];

        if (!deviceManager.isMidiInputDeviceEnabled(newInput.identifier))
            deviceManager.setMidiInputDeviceEnabled(newInput.identifier, true);

        deviceManager.addMidiInputDeviceCallback(newInput.identifier, this);
        inputSelector.setSelectedId(index + 1, juce::dontSendNotification);

        lastInputIndex = index;
    }


    //==============================================================================
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(MyMidiComponent)
};
}

Is this the full code? There is nothing calling the function sendMidiMessage() it seems.

True, that is in a different .h file, but not important for the issue as sendMidiMessage() gets triggered and works fine except that updating of the combobox

That function is also presently set ignore the incoming port name.

Yeas As I don’t have the same midi devices on this development machine. So setting it to 1 should at least select the second port (1st is MS GS wavetable, 2nd Roland UM-ONE). And The Button works when using the fixed value for testing.

Can you show the code that populates the ComboBox?

Nevermind, I see it.

Yeah and clicking the combobox and selecting works fine. Also when using the SET button.
I tried to add myButton.triggered(), which does cals the onClick, but same results als trying to use the setSelectedId(), as in it gets triggered but not updating. As it reads current selected text, which obviously isn’t set yet.