Cannot display menu using MenuBarModel

I am new to JUCE, and am trying to display a menu on top of the window using MenuBarModel.

If the menu worked, it would have these options:
image

I have checked all relevant examples such as MenusDemo.h, but cannot get the menu to display.

Here is my code in case I am missing something obvious:

/*
==============================================================================
    This is PluginEditor.h
==============================================================================
*/

#pragma once

#include <JuceHeader.h>
#include "PluginProcessor.h"

class MyMenu : public juce::Component,
    public juce::ApplicationCommandTarget,
    public juce::MenuBarModel
{
public:

    enum CommandIDs
    {
        newProject = 1,
        openProject,
        saveProject,
        importFile,
        exportFile,
        undo,
        redo,
        option1,
        option2
    };

    MyMenu()
    {
        menuComponent.reset(new juce::MenuBarComponent(this));
        addAndMakeVisible(menuComponent.get());

        setApplicationCommandManagerToWatch(&commandManager);
        commandManager.registerAllCommandsForTarget(this);

        // this ensures that commands invoked on the DemoRunner application are correctly forwarded to this demo
        commandManager.setFirstCommandTarget(this);

        // this lets the command manager use keypresses that arrive in our window to send out commands
        addKeyListener(commandManager.getKeyMappings());

        setWantsKeyboardFocus(true);

        //setSize(500, 500);
        #if JUCE_MAC
            MenuBarModel::setMacMainMenu(this);
        #endif
    }
    ~MyMenu() override
    {
        #if JUCE_MAC
                MenuBarModel::setMacMainMenu(nullptr);
        #endif
        commandManager.setFirstCommandTarget(nullptr);
    }

    juce::StringArray getMenuBarNames() override;
    juce::PopupMenu getMenuForIndex(int index, const juce::String& name) override;
    void menuItemSelected(int menuID, int index) override;
    ApplicationCommandTarget* getNextCommandTarget() override;
    void getAllCommands(juce::Array<juce::CommandID>& c) override;
    void getCommandInfo(juce::CommandID commandID, juce::ApplicationCommandInfo& result) override;
    bool perform(const InvocationInfo& info) override;
private:
    #if JUCE_DEMO_RUNNER
        juce::ApplicationCommandManager& commandManager = getGlobalCommandManager();
    #else
        juce::ApplicationCommandManager commandManager;
    #endif
    std::unique_ptr<juce::MenuBarComponent> menuComponent;

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MyMenu)
};


//==============================================================================
/**
*/
class MyAudioProcessorEditor  : public juce::AudioProcessorEditor
{
public:
    MyAudioProcessorEditor (MyAudioProcessor&);
    ~MyAudioProcessorEditor() override;

    //==============================================================================
    void paint (juce::Graphics&) override;
    void resized() override;

private:
    // This reference is provided as a quick way for your editor to
    // access the processor object that created it.
    MyAudioProcessor& audioProcessor;

    std::unique_ptr<MyMenu> menuBar;

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MyAudioProcessorEditor)
};
/*
==============================================================================
    This is PluginEditor.cpp
==============================================================================
*/

#include "PluginProcessor.h"
#include "PluginEditor.h"

juce::StringArray MyMenu::getMenuBarNames()
{
    return { "File", "Edit", "Align" };
}

juce::PopupMenu MyMenu::getMenuForIndex(int index, const juce::String& name)
{
    juce::PopupMenu menu;
    switch (index) {
    case 0: // File menu
        menu.addCommandItem(&commandManager, CommandIDs::newProject);
        menu.addCommandItem(&commandManager, CommandIDs::openProject);
        menu.addCommandItem(&commandManager, CommandIDs::saveProject);
        menu.addCommandItem(&commandManager, CommandIDs::importFile);
        menu.addCommandItem(&commandManager, CommandIDs::exportFile);
        break;
    case 1: // Edit menu
        menu.addCommandItem(&commandManager, CommandIDs::undo);
        menu.addCommandItem(&commandManager, CommandIDs::redo);
        break;
    case 2: // Align menu
        menu.addCommandItem(&commandManager, CommandIDs::option1);
        menu.addCommandItem(&commandManager, CommandIDs::option2);
        break;
    }
    return menu;
}

void MyMenu::menuItemSelected(int menuID, int index) {} // todo

juce::ApplicationCommandTarget* MyMenu::getNextCommandTarget()
{
    return this; // findFirstTargetParentComponent()
}

void MyMenu::getAllCommands(juce::Array<juce::CommandID>& c)
{
    juce::Array<juce::CommandID> commands
    {
        CommandIDs::newProject,
        CommandIDs::openProject,
        CommandIDs::saveProject,
        CommandIDs::importFile,
        CommandIDs::exportFile,
        CommandIDs::undo,
        CommandIDs::redo,
        CommandIDs::option1,
        CommandIDs::option2
    };
    c.addArray(commands);
}

void MyMenu::getCommandInfo(juce::CommandID commandID, juce::ApplicationCommandInfo& result)
{
    switch (commandID)
    {
    case CommandIDs::newProject:
        result.setInfo("New", "TODO", "File", 0);
        result.addDefaultKeypress('n', juce::ModifierKeys::ctrlModifier);
        break;
    case CommandIDs::openProject:
        result.setInfo("Open", "TODO", "File", 0);
        result.addDefaultKeypress('o', juce::ModifierKeys::ctrlModifier);
        break;
    case CommandIDs::saveProject:
        result.setInfo("Save", "TODO", "File", 0);
        result.addDefaultKeypress('s', juce::ModifierKeys::ctrlModifier);
        break;
    case CommandIDs::importFile:
        result.setInfo("Import", "TODO", "File", 0);
        result.addDefaultKeypress('i', juce::ModifierKeys::ctrlModifier);
        break;
    case CommandIDs::exportFile:
        result.setInfo("Export", "TODO", "File", 0);
        result.addDefaultKeypress('e', juce::ModifierKeys::ctrlModifier);
        break;
    case CommandIDs::undo:
        result.setInfo("Undo", "TODO", "Edit", 0);
        result.addDefaultKeypress('z', juce::ModifierKeys::ctrlModifier);
        break;
    case CommandIDs::redo:
        result.setInfo("Redo", "TODO", "Edit", 0);
        result.addDefaultKeypress('y', juce::ModifierKeys::ctrlModifier);
        break;
    case CommandIDs::option1:
        result.setInfo("Option 1", "TODO", "Align", 0);
        result.addDefaultKeypress('g', juce::ModifierKeys::ctrlModifier);
        break;
    case CommandIDs::option2:
        result.setInfo("Option 2", "TODO", "Align", 0);
        result.addDefaultKeypress('w', juce::ModifierKeys::ctrlModifier);
        break;
    }
}

bool MyMenu::perform(const InvocationInfo& info)
{
    // todo: make commands perform actions
    return true;
}

//==============================================================================
MyAudioProcessorEditor::MyAudioProcessorEditor (MyAudioProcessor& p)
    : AudioProcessorEditor (&p), audioProcessor (p)
{
    // Make sure that before the constructor has finished, you've set the editor's size to whatever you need it to be.
    menuBar.reset(new MyMenu());
    addAndMakeVisible(menuBar.get());

    setSize (1280, 720);
}

MyAudioProcessorEditor::~MyAudioProcessorEditor() {}

//==============================================================================
void MyAudioProcessorEditor::paint (juce::Graphics& g)
{
    // (Our component is opaque, so we must completely fill the background with a solid colour)
    g.fillAll (getLookAndFeel().findColour (juce::ResizableWindow::backgroundColourId));
    g.setColour (juce::Colours::white);
    g.setFont (16.0f);
    g.drawFittedText ("Where is the menu", getLocalBounds(), juce::Justification::centred, 1);
}

void MyAudioProcessorEditor::resized()
{
    // This is generally where you'll want to lay out the positions of any subcomponents in your editor..

    auto bounds = getLocalBounds();
    //menuBar->setBounds(50, 50, getWidth(), 20);
    menuBar->setBounds(bounds.removeFromTop(getLookAndFeel().getDefaultMenuBarHeight()));
}

I am compiling on Windows, so setMacMainMenu is not the cause of the problem.

I use addAndMakeVisible on both the MenuBarComponent and MyMenu.
I don’t know if both are necessary, but there is no difference if either are removed.

I have no idea what I’m doing wrong, so any help would be extremely useful!

You need to override resized in the MyMenu class:

    void resized() override { menuComponent->setBounds (getLocalBounds()); }
1 Like

Thank you so much, this fixed the problem instantly!

I was wondering if this code snippet causes any issues due to the other resize() method I have:

menuBar->setBounds(bounds.removeFromTop(getLookAndFeel().getDefaultMenuBarHeight()));

Should I do something similar for MyMenu or is it OK to use getLocalBounds()?

I think that looks fine as it is.

1 Like