Table of Contents
This tutorial introduces the hierarchical nature of the Component class whereby one component can contain one or more nested child components.
This is key to laying out user interfaces in JUCE.
Platforms: Windows, Mac OS X, Linux, iOS, Android
- This tutorial leads on from Tutorial: The Graphics class, which you should have read and understood first.
Download the demo project for this tutorial here: tutorial_component_parents_children.zip. Unzip the project and open it in your IDE.
If you need help with this step, see Tutorial: Getting started with the Projucer.
The demo project displays a scene containing a simple drawing of a house, as shown in the following screenshot:
Does that look familiar? It's rather similar to the end result of Tutorial: The Graphics class! The difference here is that each of the parts is drawn into a separate Component object using separate
paint() functions. As we will see, these are grouped logically. For example, the wall of the house and its roof are grouped into a single "house" object.
Let's explore how this is put together and why it is a good idea to structure your components in this way.
Most user interfaces comprise a number of elements, such as pieces of text, buttons, sliders, menus, and so on. For example, the following screenshot shows the AudioDeviceSelectorComponent class (which is for controlling audio hardware settings, see Tutorial: The AudioDeviceManager class for more information). This contains a button, some labels, some menus (combo boxes), some radio buttons, and an audio level indicator.
Some individual user interface elements may also group together other user interface elements to form more useful controls. For example, the JUCE Slider class can contain not only the slider itself but also a text box that shows the slider's current value. This is shown in the following screenshot:
In each of these cases, by separating the individual elements into separate parts of hierarchy, it is much easier design the layout of the interface (and respond to user interaction). Some components may use their
paint() function to draw themselves. Other components may simply contain other components. Some components may contain other components and perform some drawing. The design choices are quite flexible.
In this tutorial the
MainContentComponent class contains an instance of another component class as a member. This is the
SceneComponent class, which draws the actual scene. Look at the
MainComponent.h file within the project. A SceneComponent object is added as a private member:
#include directive including the header file for the
SceneComponent class at the top:
MainContentComponent constructor, this
SceneComponent object is added as a child component and the
MainContentComponent object becomes its parent.
- A child component must have one parent at any one time. You can remove a child component from its parent and then add it to another parent if you wish.
In order for the child component to be displayed, it also needs to be made visible. These two steps can be done separately, but it is a common idiom in JUCE to perform both of these actions in one single step using the Component::addAndMakeVisible() function:
MainContentComponent class sets its own size during construction, many component objects initially have a zero size. The call to the Component::setSize() function will in turn trigger a call to our
MainContentComponent::resized() function. This is a good place to set the size and position of any child components:
The important point here is that the coordinates in the call to the
SceneComponent::setBounds() function are relative to its parent component (in this case our
MainContentComponent object). What this means is that the top-left corner of the parent component is point (0, 0) and the child component will be positioned such that its top-left corner will be relative to this point. In fact our
SceneComponent object fills the entire content of our
MainContentComponent object. An alternative way to write this is to use the Component::getLocalBounds() function. This returns a Rectangle object representing the bounds of the component that calls it. This results in a rectangle with a position (0, 0) and a size that its width and height. This Rectangle object can then be passed to the
SceneComponent::setBounds() function. The alternative code is shown in the following code snippet:
The next section of this tutorial reflects the structure of this
- Child components may be positioned to exceed the bounds of the parent component but everything outside the parent component's bounds will not be drawn. If you can't see your component, make sure that the bounds have been set properly (for example, in the parent component’s
SceneComponent class does some of its own drawing and contains two child components (representing the floor and the house). The
SceneComponent declaration (in the
SceneComponent.h file) is as follows:
HouseComponent objects are added and made visible in the constructor:
To draw the sky, we fill the entire component with light blue in the
The floor and the house have their bounds positioned within the
- A component draws itself first, then child components are drawn on top of this. You can override the Component::paintOverChildren() function if you need to draw on top of child components. Child components are drawn in the order that they were added to the parent component. This can be adjusted later by using the functions Component::toFront(), Component::toBack(), Component::toBehind(), or Component::setAlwaysOnTop()
Let's look at how the floor and the house are drawn within the respective classes.
The floor is drawn as a green horizontal line (as in Tutorial: The Graphics class) five pixels thick, centred vertically within the component, and spanning its full width. Here is the
FloorComponent::paint() function (from the
The house itself doesn't perform any drawing of its own (it does not have a
paint() function) but comprises two other components (representing the wall and roof of the house) in the
RoofComponent objects are added and made visible in the constructor:
These are positioned proportionally in the
- : First we calculate the separation between the roof and wall. Let's make this 1⁄20 of the height of the house but make it no smaller than 2 pixels — using the jlimit() function. This so that there is always a gap between the roof and the wall even when the height is small. When the height is large then the gap remains proportional to the height.
- : The roof is set to be the full width of the house and 1⁄5 of the height of the house. This is adjusted to account for the separation.
- : The wall is positioned under the roof occupying 4⁄5 of the height of the house. This is also adjusted to account for the separation.
WallComponent class is simple. It just fills itself with a checkerboard pattern in the
WallComponent::paint() function (from the
RoofComponent class draws a triangle using a Path object in the
RoofComponent::paint() function (in the
If we call the width of the
RoofComponent object w and the height h then the three points that make up the triangle are: (0, h), (w, h), (w⁄2, 0).
- Try changing the positions of objects within the scene.
Let's add a sun to our scene. A number of empty functions are provided for you in the
SunComponent.h file, to which we will add some code in a moment.
First, we need to make some changes to the
- : Include the
- : Add an instance of the
SunComponentclass to the private section.
Then we need to add the sun and make it visible :
And position the sun in the top-right corner :
We need to add the drawing code to the
SunComponent::paint() function (in the
Notice that we need to position the ellipse slightly within the bounds of the component. This should be dependent on the line thickness. This is because it is the centre of the line that sits exactly on the coordinates specified. For example, if we draw a line on an edge of the component, then half of the thickness of the line will sit outside the bounds of the component. Look as the following screenshot to see what could happen if we didn't adjust the position and size of the ellipse slightly.
- Another way around this is to use the Component::setPaintingIsUnclipped() function to allow the component to draw beyond its bounds. Make sure you read the API documenation, as there are some caveats in the use of this function.
Our final scene should look like this:
- The changed code for this step can be found in the files
Sourcedirectory of the demo project.
- Using the code above, the sun is always drawn as an ellipse. Since we specify the
SunComponentobject to be a square, we don't notice this potential problem. Fix the
SunComponentclass such that it always draws a circle within its bounds — rather than an ellipse — even if its width and height aren't the same.
One of the main benefits of the coordinate system used by the Component class, is that drawing is always performed relative to the top-left of the component. Another benefit of encapsulating drawing into a separate class is that it can be reused easily.
For example, we can easily add another house  to the
Then add it and make it visible  in the
And position it  in the
With the small house added, the scene should look like this:
- The changed code for this final step can be found in the files
Sourcedirectory of the demo project.
- Create a new class
StreetComponentthat contains a number of
HouseComponentobjects in a row to form a street, and add it to the project. Modify the
SceneComponentclass such that it contains some streets and individual houses.
In this tutorial we have introduced the hierarchical parent and child system that is used by the Component class. In particular, we have learned:
- How to use the Component::addAndMakeVisible() function to add child components to other components.
- How to position and size child components relative to their parent components.
- That components can do their own drawing, contain child component that perform drawing, or both.
- That components perform their drawing first, then child components are painted in the order that they were added to the parent.
- Drawing is normally clipped to the component's bounds.
Generated on Fri Jan 12 2018 09:51:15 for JUCE by 1.8.13