Is this the right approach to having multiple windows in a GUI App?

I’m new to JUCE, studying tutorials, and before I go too far down this road I’d like to run it by you good folks.

Let’s say I want to have a GUI App with multiple Editor Windows, each controlling different sets of parameters.

It seems that each Window must own a Component that displays content for that window.

So is the right approach to declare a custom DocumentWindow class for each Editor Window, and then in that class declare a Component that the window will own?

I started with a default GUI App. I added two Component .h files - one for Window2 and one for Window3. For example, here is the Window2 and Window2Component code (Window3.h is exactly the same with a different color border):

#pragma once

#include "../JuceLibraryCode/JuceHeader.h"

//==============================================================================
/*
*/
//==============================================================================

class Window2Component    : public Component
{
public:
    Window2Component()
    {
        // In your constructor, you should add any child components, and
        // initialise any special settings that your component needs.
        
        setSize (getWidth(), getHeight());
        addAndMakeVisible (window2Button);
    }

    ~Window2Component()
    {
    }

    void paint (Graphics& g) override
    {
        /* This demo code just fills the component's background and
           draws some placeholder text to get you started.

           You should replace everything in this method with your own
           drawing code..
        */

        g.fillAll (getLookAndFeel().findColour (ResizableWindow::backgroundColourId));   // clear the background

        g.setColour (Colours::red);
        g.drawRect (getLocalBounds(), 2);   // draw an outline around the component

        g.setColour (Colours::white);
        g.setFont (14.0f);
        g.drawText ("Window2Component", getLocalBounds(),
                    Justification::centred, true);   // draw some placeholder text
    }

    void resized() override
    {
        // This method is where you should set the bounds of any child
        // components that your component contains..
 
        window2Button.setBounds (20, 20, 140, 40);
    }

private:
    TextButton window2Button   { " Button in Window 2" };

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Window2Component)
};

// DocumentWindow that handles close button by hiding the window
class Window2   : public DocumentWindow
{
public:
    Window2 (const String& name, Colour backgroundColour, int buttonsNeeded)
    : DocumentWindow (name, backgroundColour, buttonsNeeded)
    {
        setBounds (20, 20, 300, 400);
        setResizable (true, false);
        setUsingNativeTitleBar (true);
        setContentOwned (&window2Component, false);
    }
    
    void closeButtonPressed()
    {
        setVisible(false);
    }
    
private:
    Window2Component window2Component;
    
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Window2)
};

The MainComponent:

#pragma once

#include "../JuceLibraryCode/JuceHeader.h"

//==============================================================================
/*
    This component lives inside our window, and this is where you should put all
    your controls and content.
*/
class MainComponent   : public Component
{
public:
    //==============================================================================
    MainComponent()
    {
        setSize (600, 400);
        addAndMakeVisible (openWindow2Button);
        addAndMakeVisible (openWindow3Button);
        openWindow2Button.onClick = [this] { openWindow2(); };
        openWindow3Button.onClick = [this] { openWindow3(); };
    }

    ~MainComponent()
    {
    }

    //==============================================================================
    void paint (Graphics& g) override
    {
        // (Our component is opaque, so we must completely fill the background with a solid colour)
        g.fillAll (getLookAndFeel().findColour (ResizableWindow::backgroundColourId));

        g.setFont (Font (16.0f));
        g.setColour (Colours::white);
        g.drawText ("Hello World!", getLocalBounds(), Justification::centred, true);
    }

    void resized() override
    {
        // This is called when the MainComponent is resized.
        // If you add any child components, this is where you should
        // update their positions.
        
        openWindow2Button.setBounds (20, 20, 140, 40);
        openWindow3Button.setBounds (20+140, 20, 140, 40);
    }


private:
    void openWindow2()
    {
        if (window2 == nullptr){
            window2.reset( new Window2 ("Window 2",
                                             Desktop::getInstance().getDefaultLookAndFeel().
                                             findColour(ResizableWindow::backgroundColourId),
                                             DocumentWindow::allButtons));
        }
        window2->setVisible (true);
    }
    void openWindow3()
    {
        if (window3 == nullptr){
            window3.reset( new Window3 ("Window 3",
                                             Desktop::getInstance().getDefaultLookAndFeel().
                                             findColour(ResizableWindow::backgroundColourId),
                                             DocumentWindow::allButtons));
        }
        window3->setVisible (true);
    }

    //==============================================================================
    // Your private member variables go here...
    TextButton openWindow2Button   { "Open Window 2" };
    TextButton openWindow3Button   { "Open Window 3" };
    std::unique_ptr<Window2> window2;
    std::unique_ptr<Window3> window3;

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainComponent)
};

So this results in an app that works and the two buttons each open up an independent editing window, it seems.


(I don’t want to delete each editor upon closing; just want to hide it so it can be reopened in the same location as it was last closed, etc. That’s why closeButtonPressed() simply hides it.)

Now say that you wanted the button in the Window2Component to call a function that is back in the MainContentComponent. I guess you would make the MainContentComponent a Listener, and then in the Window2Component, use .addListener() for the window2Button? How do you pass a pointer to the MainContentComponent to it?

Or… this whole approach may be wrong. I can’t find a good example for this; the Windows.h demo is really not applicable…