Getting a menu bar to work in JUCE

Hey guys, JUCE noob here. I am working on a standalone application. but after 3 days I am still not able to get the basic menu bar to work. I’ve read the MenusDemo and other things from forums which did not help me, I need to get a menu bar up and working, i.e the menu names should be visible, and there should be a pop-up when pressed n one of them, a list should be shown whose items on pressed should call a function.This is the code i have still now, which is only creating a small slit at the top for menu(probably because of the Component class) but nothing related to menus is being shown, any help would be really appreciated :

MenuComponent.h

#pragma once

#include <JuceHeader.h>

class MenuComponent :   public juce::MenuBarModel,
                        public juce::Component {
public:

    MenuComponent();
    ~MenuComponent() override;

    juce::StringArray getMenuBarNames() override;
    juce::PopupMenu getMenuForIndex(int topLevelMenuIndex, const juce::String& menuName) override;
    void menuItemSelected(int menuItemID, int topLevelMenuIndex) override;

    void paint(juce::Graphics& g) override;
    void resized() override;

private:
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MenuComponent)
};

MenuComponent.cpp :

#include "MenuComponent.h"

MenuComponent::MenuComponent() {
    setSize(getParentWidth(), 20);
    setVisible(true);
}

MenuComponent::~MenuComponent() {}

void MenuComponent::paint(juce::Graphics& g) {
    g.fillAll(juce::Colours::white);

    g.setFont (juce::Font (16.0f));
    g.setColour (juce::Colours::black);

}

void MenuComponent::resized() {
    setSize(getParentWidth(), 20);
}

juce::StringArray MenuComponent::getMenuBarNames() {
    return {"Instrument", "Settings", "Edit"};
}

juce::PopupMenu MenuComponent::getMenuForIndex(int topLevelMenuIndex, const juce::String& menuName) {
    juce::PopupMenu menu;

    if (menuName == "Instrument") {
        menu.addItem(1, "Initialize");
        menu.addSeparator();
        menu.addItem(2, "Load Instrument");
        menu.addItem(3, "Save Instrument");
        menu.addItem(4, "Save Instrument as");
        menu.addSeparator();
        menu.addItem(5, "Exit");
    } else if (menuName == "Settings") {
        menu.addItem(15, "Theme");
        menu.addItem(16, "Scale+");
        menu.addItem(17, "Scale-");
    } else if (menuName == "Edit") {
        menu.addItem(21, "undo");
    }

    return menu;
}

void MenuComponent::menuItemSelected(int menuItemID, int topLevelMenuIndex) {
    switch (menuItemID) {
        case 1:
            std::cout << "Initialize selected";
            break;
        case 5:
            std::cout << "Exit selected";
            break;
        default:
            break;
    }
}

MainComponent.cpp :

#include "MainComponent.h"
#include "MenuComponent.h"
#include <memory.h>

MainComponent::MainComponent() {
    setSize (1200, 800);

    this->menuBarComponent = std::make_unique<MenuComponent>();
    addChildComponent(this->menuBarComponent.get());
}


void MainComponent::paint (juce::Graphics& g) {
    g.fillAll (juce::Colours::brown);

    g.setFont (juce::Font (16.0f));
    g.setColour (juce::Colours::blanchedalmond);
}

void MainComponent::resized() {}

The Main.cpp is exactly left as how it is created for juce GUI, from projuicer.
I am using JUCE version 7.0.9 on linuxmint(Cinnamon)

Menu bar and command manager are a bit hard to understand at first (and even later). There’s toy projects i did to illustrate some old crashes there < GitHub - nicolasdanet/Dummy: ... > ; IIRC MenuCrash / PopupCrash examples could help you.

thanks for the reply, checking the code out now. but can you tell me what is the advantage of using a command manager over a menu bar model, do we need to implement both of them to make the menu bar work? and also how to setModel()? I am not able to get it to show the menu.

The command manager is required to handle properly keyboard shortcuts.

oh, thanks. i’ve tried out the examples from you repo(MenuCrash). changed my code to this :
MenuComponent.h :

#pragma once

#include <JuceHeader.h>

class MenuComponent :   public juce::MenuBarComponent,
                        public juce::MenuBarModel
{
public:
    //========================================
    MenuComponent();
    ~MenuComponent() override;
    //========================================
    void paint(juce::Graphics& g) override;
    void resized() override;
    //========================================
    juce::StringArray getMenuBarNames() override;
    juce::PopupMenu getMenuForIndex(int topLevelMenuIndex, const juce::String& menuName) override;
    void menuItemSelected(int menuItemID, int topLevelMenuIndex) override;

private:
    std::unique_ptr<juce::MenuBarComponent> menuComp;

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MenuComponent)
};

MenuComponent.cpp :

#include "MenuComponent.h"

MenuComponent::MenuComponent() {
    setSize(getParentWidth(), 20);

    menuComp.reset(new juce::MenuBarComponent(this));
    addAndMakeVisible(menuComp.get());
    menuComp.get()->setModel(this);

    setVisible(true);
}

MenuComponent::~MenuComponent() {}

void MenuComponent::paint(juce::Graphics& g) {
    g.fillAll(juce::Colours::white);

    g.setFont (juce::Font (16.0f));
    g.setColour (juce::Colours::black);
}

void MenuComponent::resized() {
    setSize(getParentWidth(), 20);
    menuComp.get()->setBounds(0, 0, 100, 100);
}

juce::StringArray MenuComponent::getMenuBarNames() {
    return {"Instrument", "Settings", "Edit"};
}

juce::PopupMenu MenuComponent::getMenuForIndex(int topLevelMenuIndex, const juce::String& menuName) {
    juce::PopupMenu menu;

    if (menuName == "Instrument") {
        menu.addItem(1, "Initialize");
        menu.addSeparator();
        menu.addItem(2, "Load Instrument");
        menu.addItem(3, "Save Instrument");
        menu.addItem(4, "Save Instrument as");
        menu.addSeparator();
        menu.addItem(5, "Exit");
    } else if (menuName == "Settings") {
        menu.addItem(15, "Theme");
        menu.addItem(16, "Scale+");
        menu.addItem(17, "Scale-");
    } else if (menuName == "Edit") {
        menu.addItem(21, "undo");
    }

    return menu;
}

void MenuComponent::menuItemSelected(int menuItemID, int topLevelMenuIndex) {
    switch (menuItemID) {
        case 1:
            std::cout << "Initialize selected";
            break;
        case 5:
            std::cout << "Exit selected";
            break;
        default:
            break;
    }
}

this is crashing before launching the GUI, at this line : menuComp.get()->setBounds(0, 0, 100, 100); i’ve tried using without the .get() but it is still crashing(segmentation fault). without the line it looks exactly like it was in the beginning. thanks for the help but nothing changed.

When you init MenuComponent, setSize() will call resized() and then call menuComp.get()->setBounds() before you actually init menuComp.

By the way, I would recommend declaring juce::MenuBarCompoent as a class member instead of using the smart pointer.

1 Like

I’ve tried to make it a single class again, setting the model to this, and setting the size in the resized method. as far as i understand resized() will be called even when creating the application for the first time, so we do not need to have a setSize in the init, but any kind of call to setBounds is crashing by a segmentation fault, I’ve printed out the pointer to the getModel() to make sure it is not null. I do not get it am i doing something inherently wrong? all i see is this :


My code : MenuComponent.h :

#pragma once

#include <JuceHeader.h>

class MenuComponent :   public juce::MenuBarComponent,
                        public juce::MenuBarModel
{
public:
    //========================================
    MenuComponent();
    ~MenuComponent() override;
    //========================================
    void paint(juce::Graphics& g) override;
    void resized() override;
    //========================================
    juce::StringArray getMenuBarNames() override;
    juce::PopupMenu getMenuForIndex(int topLevelMenuIndex, const juce::String& menuName) override;
    void menuItemSelected(int menuItemID, int topLevelMenuIndex) override;

private:
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MenuComponent)
};

MainComponent.cpp :

#include "MenuComponent.h"

MenuComponent::MenuComponent() {
    setModel(this);
}

MenuComponent::~MenuComponent() {}

void MenuComponent::paint(juce::Graphics& g) {
    g.fillAll(juce::Colours::white);

    g.setFont (juce::Font (16.0f));
    g.setColour (juce::Colours::black);
}

void MenuComponent::resized() {
    setSize(getParentWidth(), 20);
}

juce::StringArray MenuComponent::getMenuBarNames() {
    return {"Instrument", "Settings", "Edit"};
}

juce::PopupMenu MenuComponent::getMenuForIndex(int topLevelMenuIndex, const juce::String& menuName) {
    juce::PopupMenu menu;

    if (menuName == "Instrument") {
        menu.addItem(1, "Initialize");
        menu.addSeparator();
        menu.addItem(2, "Load Instrument");
        menu.addItem(3, "Save Instrument");
        menu.addItem(4, "Save Instrument as");
        menu.addSeparator();
        menu.addItem(5, "Exit");
    } else if (menuName == "Settings") {
        menu.addItem(15, "Theme");
        menu.addItem(16, "Scale+");
        menu.addItem(17, "Scale-");
    } else if (menuName == "Edit") {
        menu.addItem(21, "undo");
    }

    return menu;
}

void MenuComponent::menuItemSelected(int menuItemID, int topLevelMenuIndex) {
    switch (menuItemID) {
        case 1:
            std::cout << "Initialize selected";
            break;
        case 5:
            std::cout << "Exit selected";
            break;
        default:
            break;
    }
}

The whole Menubar/CommandManager thing is very hard to set up. Here is a link to another post that has some downloadable examples of how to do it:

1 Like

Thanks for the reply but I’ve already looked into it, the problem is I understand that this is complicated but what am I not doing?

  • Implemented the virtual functions.
  • setModel to this to say the MenuBarModel is the same class.
  • setSize for the display when resized.

I’ve tried to create an MenuBarComponent as a class variable and tried adding to this class, it resulted in an assertion failing for adding the same class to the class as a child, but yet most people’s code I see has something like adding this as a child. how is this feasible?

I think I got it, still do not know exactly what I am doing. will post the solution as I understand or get some clarity on it. Thank you everyone who replied.

You don’t need to implement a MenuBarComponent, that exists already.

The DocumentWindow and it’s descendants has a method setMenuBar(MenuBarModel, menuBarHeight).

Final code that is working , I’ve commented how I think it works, feel free to correct me if i am wrong.

MenuComponent.h

#pragma once

#include <JuceHeader.h>

// We do not need to implement the `MenuBarComponent`,
// it is added as a class member.
class MenuComponent :   public juce::Component,
                        public juce::MenuBarModel
{
public:
    //========================================
    MenuComponent();
    ~MenuComponent() override;
    //========================================
    void paint(juce::Graphics& g) override;
    void resized() override;
    //========================================
    juce::StringArray getMenuBarNames() override;
    juce::PopupMenu getMenuForIndex(int topLevelMenuIndex, const juce::String& menuName) override;
    void menuItemSelected(int menuItemID, int topLevelMenuIndex) override;

private:
    std::unique_ptr<juce::MenuBarComponent> menuComponent;

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MenuComponent)
};

MenuComponent.cpp

#include "MenuComponent.h"

MenuComponent::MenuComponent() {
    // `this` already implements the `MenuBarModel` so this is going to return a
    // `MenuBarComponent` with the Model already setup.
    menuComponent.reset(new juce::MenuBarComponent(this));
    // getting the new component to show up.
    addAndMakeVisible(menuComponent.get());
    resized(); // this is NEEDED for the first time rendering.
}

MenuComponent::~MenuComponent() {}

void MenuComponent::paint(juce::Graphics& g) {
    g.fillAll(juce::Colours::beige);

    g.setFont (juce::Font (20.0f));
    g.setColour (juce::Colours::black);
}

void MenuComponent::resized() {
    //setting the size of our normal component.
    setSize(getParentWidth(), 20);
    // setting the size of the menuComponent inside this component.
    menuComponent.get()->setSize(getParentWidth(), 20);
}

juce::StringArray MenuComponent::getMenuBarNames() {
    return {"Instrument", "Settings", "Edit"};
}

juce::PopupMenu MenuComponent::getMenuForIndex(int topLevelMenuIndex, const juce::String& menuName) {
    juce::PopupMenu menu;

    if (menuName == "Instrument") {
        menu.addItem(1, "Initialize");
        menu.addSeparator();
        menu.addItem(2, "Load");
        menu.addItem(3, "Save");
        menu.addItem(4, "Save as");
        menu.addSeparator();
        menu.addItem(5, "Exit");
    } else if (menuName == "Settings") {
        menu.addItem(15, "Theme");
        menu.addItem(16, "Scale+");
        menu.addItem(17, "Scale-");
    } else if (menuName == "Edit") {
        menu.addItem(21, "undo");
    }

    return menu;
}

void MenuComponent::menuItemSelected(int menuItemID, int topLevelMenuIndex) {
    switch (menuItemID) {
        case 1:
            std::cout << "Initialize selected" << "\n";
            break;
        case 5:
            std::cout << "Exit selected";
            break;
        default:
            break;
    }
}

@daniel This works for now, will change if needed. thanks for the reply.