Passing components to a DocumentWindow

Would love to know if I’m approaching this correctly or not.

I would like to swap between different views. Not by opening multiple windows but by exchanging the component that is displayed in a window.

My guess is that this is most easily done by passing the component to the MainWindow that derives from DocumentWindow using the setContentOwned method.

My attempts so far is:

  1. Create 2 UI components that i’d like to swap between and give each a button that when clicked does the following:
void FirstComponent::buttonClicked(Button* button)
{
	auto activeWindow = juce::TopLevelWindow::getActiveTopLevelWindow();
	if(activeWindow)
	{
		auto window = dynamic_cast<MainWindow*>(activeWindow);
		window->setContentOwned(new SecondView(), true);
	}
}

and similarly in the other UI Component:

void SecondComponent::buttonClicked(Button* button)
{
	auto activeWindow = juce::TopLevelWindow::getActiveTopLevelWindow();
	if(activeWindow)
	{
		auto window = dynamic_cast<MainWindow*>(activeWindow);
		window->setContentOwned(new FirstView(), true);
	}
}

Does this seem like a reasonable way to get it done? Perhaps I shouldn’t be ‘newing-up’ the components like this? In the juce Windows Demo project the component being passed to the DocumentWindow via setContentOwned() was being passed like:

setContentOwned (&selector, false);
where the selector was not being instantiated with ‘new’ but as a private member:

	ColourSelector selector  { ColourSelector::showColourAtTop
                             | ColourSelector::showSliders
                             | ColourSelector::showColourspace };

Would really like to hear how this type of thing is being handled in the wild.

This sounds like the demo is incorrect… setContentOwned() means the window is taking ownership, and will call delete on the component when the window is destroyed. I don’t get any kinds of errors when running that demo, but usually you would use setContentOwned() for a Component that was heap allocated.

Is there a reason you think you shouldn’t? Your current view-swapping looks fine to me.

You could also just always have both components around and use setContentNonOwned() when you want to swap them. It’s really a matter of whether or not you want both components to exist in at all times or if you really only need the “active” one to be persistent in memory.

1 Like

Thanks for the feedback :slight_smile: I think the demo is launching multiple windows rather than swapping components between a single DocumentWindow. The selector component that is passed to setContentOwned is a private member of the DocumentWindow that is using it - perhaps that makes it valid to be done this way?

Good call on using setContentNotOwned - I was just beginning to consider if there was a way to change screens but be able to switch back and have the same state kept alive. But then what is the correct way to delete the components when you shut the app down?

I think it’s more that “it works” than that it’s valid, as setContentOwned() is supposed to be for heap allocated components. Because it’s owned, when the window is torn down it will delete the selector, but the selector wasn’t a heap allocated component. The only reason there isn’t a problem is because nobody is accessing selector after the delete call is made, since the window itself is being destructed at that time… just something to keep in mind.

“Using delete on a pointer to an object not allocated with new gives unpredictable results.”, via Microsoft’s Developer Docs.

If your components are members of a class like the window or the application itself, they are automatically destructed when the class holding them is destructed. This is due to the objects going out of scope, since the class holding them has gone out of scope (stack allocated) or has been deleted (heap allocated). You only need to delete or free memory for things you allocated using new or malloc (respectively).

For heap allocated GUI objects, usually one of the go-to approaches is to use something like std::unique_ptr so that the deletion is handled when the pointer goes out of scope. Here’s an example:


int main()
{
    // Make a std::unique_ptr which wraps a juce::Component*
    std::unique_ptr<juce::Component> myComponent = std::make_unique<juce::Component>();

    // We assign the std::unique_ptr using a different std::unique_ptr, which causes
    // the original juce::Component to be deleted and the pointer now points to our newly 
    // made juce::Component. std::make_unique passes its arguments to the constructor of
    // type of object you're creating.
    myComponent = std::make_unique<juce::Component>("My Component's Name");

    {
        // You can also give std::unique_ptr an object created via `new`
        std::unique_ptr<juce::TextButton> myButton(new juce::TextButton());

    } // myButton goes out of scope here, deleting the juce::TextButton we gave it

} // myComponent goes out of scope here, deleting the juce::Component we gave it

https://en.cppreference.com/w/cpp/memory/unique_ptr

1 Like

Fantastic! I will have a play around with this stuff and see what happens.

I watched a Juce tutorial about UI where the presenter spoke about adopting an MVC approach to Juce GUIs. Sadly he didn’t go into much detail and only showed a View and a Controller without explaining how to set up the plumbing for it. I’d love to structure things around Views and Controllers as i’m so used to it in my day job. I don’t suppose you do anything similar yourself?

1 Like

The Model portion of an MVC can be implemented using juce::ValueTree which has some really awesome features.

https://docs.juce.com/master/tutorial_value_tree.html

The talk doesn’t go into a lot of detail on integrating GUI with ValueTree, but generally speaking what you can do is have your GUI objects keep their own references to ValueTree nodes and use the ValueTree::Listener interface to receive updates.

For example ValueTree::getChildWithName() will give you back a ValueTree object, and this ValueTree is essentially just a reference back to the underlying data. When you update this reference it goes and updates the state in the main tree, and any listeners of the tree will be notified of the change.

You can use a similar approach with the existing JUCE widgets (sliders, buttons, labels, etc.) as nearly all of them expose their own juce::Value object that manages its current value. Combine this with the methods ValueTree::getPropertyAsValue() and Value::referTo and you can directly connect a slider, label, etc. so it can get/set a property in a ValueTree automatically!

1 Like

Great stuff! Digging into Value Trees is next on my list then.

Regarding the unique pointers, it seems like I can’t pass them to the setContentNonOwned method.

std::unique_ptr<T>::get() will return the actual T* (or nullptr if it’s deleted or hasn’t been set!)

1 Like

Aha! magic :smile:

1 Like

Hmm, I guess I celebrated too soon - I tried this:

void MainComponent::buttonClicked(Button* button)
{
	std::unique_ptr<TestComponent> myTestComponent( new TestComponent());

    auto activeWindow = juce::TopLevelWindow::getActiveTopLevelWindow();
	if(activeWindow)
	{
		auto window = dynamic_cast<MainWindow*>(activeWindow);
		window->setContentNonOwned(myTestComponent.get(), true);
	}
	
}

Which compiles, and when I debug I see that myTestComponent is not null - but when I click the button nothing is displayed in the window. Is this because the pointer has gone out of scope?

Yes, if you want both components to stick around, you’ll have to make add the std::unique_ptr objects as members of something that has a longer lifetime. Something like the base application object or the MainComponent.

If you only need one component “alive” at a time I would just go with passing raw pointers to setContentOwned, especially once if the structure of the app will follow MVC. Views generally aren’t (or shouldn’t be) storing their own state, but instead they are pointed to the data they should read/write from… so they’re usually created/deleted at whim

1 Like

Right, got it - I think you’re right, I would be better off learning about these Value Trees so that I can avoid having to keep the views alive by making them belong to other objects just for that purpose. Again, thanks for all the help - I really appreciate it. :wink:

1 Like