Non-graphical Components


#1

The JUCE tutorial on The Main Component says that " practically all visible elements of the GUI… are components". Are non-visible elements of program also referred to as components?

I’m asking because I’m trying to maintain a division between the GUI and the logic/core elements of my program, but I’m not sure where the core elements should go. In the template that the Projucer generates, should I add logical / non-graphical elements as private elements of the MainComponent class? Or of the XxxApplication class inside main.cpp? Or somewhere else?

Hope this question makes sense; I can elaborate if not.


#2

Often in general programming parlance, yes, but in Juce “Component” only means a GUI object. The possible exception the tutorial may be referring to, could be the base Component class, which by itself is not “visible” because its paint method does not do anything. However, it should only be inherited from in order to implement a GUI object with graphics and/or user interaction. (Or to hold instances of other components as child components.)

If you want complete division between the “core” and GUI parts of your program, the MainComponent is not the best place to hold the core objects, the Application subclass could be better, but that itself depends on the JUCE GUI module.


#3

Thanks @Xenakios, this is what I expected.

In order to ensure that the non-graphical elements of my program are outside of the Component hierarchy, does it make sense for me to add them as private members of the XxxApplication class in main.cpp? So that the end of the main.cpp file looks something like this:

class TestApplication : public JUCEApplication
{
...
private:
std::unique_ptr<MainWindow> mainWindow;
std::unique_ptr<TestProgramCore> testProgramCore;
};

#4

Yes, that looks OK for keeping the core object instance outside the Component hierarchy but that is not enough to not have a dependency on JUCE’s GUI module. (Which you might want to get rid of, if you want to be able to build and use your program as a command line utility or similar. That said, I myself have done a command line program that depends on the GUI module and haven’t had problems with that. The code does not explicitly instantiate any GUI objects but the GUI module is present because it is required by other modules.)


#5

I’m not so concerned atm about keeping any of my code independent of JUCE modules, so this method will do nicely.

New problem: I need to send a pointer from testProgramCore to mainWindow, so that the GUI can access the core’s API. What is the best way to do that? I can see two options:

  1. Passing the pointer into mainWindow through an argument in the constructor. This method works, and I’ve found some precedence for it, though it seems convoluted.
  2. Building a special function into the initialize() in Main.cpp to retrieve and set the pointer. I’ve tried this: mainWindow.get()->getContentComponent()->setPtr(testProgramCore.get());
    where setPtr(...) is a function defined inside MainComponent, but it doesn’t work, because getContentComponent() retrieves the base class.

Is option 1 acceptable, or is there another option that I’m missing?

Sorry if this is a C++ basics question. I’m in over my head, but I’m also learning a great deal as I go.


#6

Passing via the constructor is the usual way and easiest to implement. What do you find convoluted about it?


#7

I find it convoluted because I’m having to pass the pointer up to every level of the GUI hierarchy individually, and I thought that there might be an easier way of bypassing the middle levels and going straight to the top. But if this is standard practice then I’ll happily go with it.


#8

Yes, this is a classic annoying problem when dealing with object trees. But I wouldn’t call it “convoluted”. :slight_smile:


#9

I’m just glad that what I’m encountering is a “classic problem” and not my own thick headedness!


#10

If you want a quick and dirty approach to avoid having to pass a pointer through every constructor you can use a static or free function that wraps a call to JUCEApplication::getInstance and some RTTI. You’d have to make the testProgramCore public or provide a getter however. Something like this:

class TestApplication : public JUCEApplication
{
public:
//...
std::unique_ptr<TestProgramCore> core;

    static TestProgramCore* getInstanceCore() {
        auto* instance = dynamic_cast<TestApplication*> (JUCEApplication::getInstance());
        return instance->core.get()
    }
}

Of course that’s probably not the best approach if the application grows in complexity and you want to start sending things to it from different threads.


#11

Since you already do the effort to split your architecture into several components (not juce::Component), I would suggest not to use these kind of simplifications.

Give yourself a strict set of rules, design decisions you are willing to follow, e.g.

  • the core may exist without the screen components, but not the other way round
    that follows, that the GUI can have references to parts of the core, but not the other way round. Now you can create the core before the GUI and there is no problem, when they are destructed in the reverse order

  • only give a GUI component the minimum information
    e.g. there is an object “Foo” in the core, and a “FooComponent”, give it only a reference to Foo, not a reference to the core itself and the FooComponent has to traverse to the actual Foo. That way you can change the hierarchy inside the core, without changing anything in the FooComponent

  • use the observer pattern, if the Foo changes, to notify FooComponent to update, e.g. with the ChangeBroadcast and ChangeBroadcastListener. If you remove the listener in the destructor, there is no chance, that the Foo calls a non-existent FooComponent

and so on.
Obviously it is totally up to you to compromise and use as much overhead as you see fit.