Tutorial: Listeners and broadcasters

This tutorial introduces a key concept in JUCE: the listener and broadcaster system.

We look at this through implementing simple actions in response to button clicks.

Level: Beginner

Platforms: Windows, Mac OS X, Linux, iOS, Android

Classes: Button, TextButton, Button::Listener, Time

Getting started

Download the demo project for this tutorial here: tutorial_listeners_and_broadcasters.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

The demo project for this tutorial presents a simple user interface with a single button and a single label. The interface should look similar to the following screenshot:

tutorial_listeners_and_broadcasters_screenshot1.png
A basic button and label interface.

The interface doesn't do anything in the state provided. We are going to add some code to make a click of the button cause the label to display the current date and time.

Configuring the interface

The MainContentComponent class comprises two child components: a TextButton object and a Label object. A TextButton object can display a button containing some specific text, and a Label object can display a piece of text.

Note
The TextButton class implements one type of button. There are many types of Button class available in JUCE. See the API reference documentation for the ToggleButton, ShapeButton, ImageButton, DrawableButton, and ArrowButton classes for more information.

The declaration (in the MainComponent.h file) for the MainContentComponent class is as follows:

class MainContentComponent : public Component
{
public:
//==============================================================================
MainContentComponent()
{
// ...
}
~MainContentComponent()
{
// ...
}
void resized() override
{
// ...
}
private:
TextButton checkTheTimeButton;
Label timeLabel;
//==============================================================================
};

The button and the label are added to the MainContentComponent object and made visible in the MainContentComponent constructor:

MainContentComponent()
{
addAndMakeVisible (checkTheTimeButton);
checkTheTimeButton.setButtonText ("Check the time...");
addAndMakeVisible (timeLabel);
timeLabel.setColour (Label::backgroundColourId, Colours::black);
timeLabel.setColour (Label::textColourId, Colours::white);
timeLabel.setJustificationType (Justification::centred);
setSize (600, 110);
}

Here we set the button text and configure a specific appearance for the label. This so that the label shows white text on a black background. By default, the label will not show any text.

Adding a listener base class

In JUCE, buttons, sliders, and many other types of controls that may need to inform other objects about a change in their state are a type of broadcaster object. In order to respond to changes in a broadcaster object, other classes need to be a listener for that specific type of broadcaster. Listeners also need to be registered with at least one specific broadcaster object of that type. (The broadcaster-listener system in JUCE follows the observer pattern.) Many broadcaster objects contain a nested Listener class, from which we can inherit, in order to become a listener for that type of broadcaster. For example, the Button class contains a nested class Button::Listener for this purpose.

Note
The Button::Listener class can be used to listen to any of the different button types including instances of the TextButton class as shown here.

To use the Button::Listener class we need to add it as a base class. In our case, we need to add the Button::Listener class as a base class of the MainContentComponent class [1]:

class MainContentComponent : public Component,
public Button::Listener // [1]
{
public:
// ...

Custom classes can become listeners to different types of broadcaster by adding more listener base classes in the same way.

Declaring the listener's callback

Each listener class has at least one pure virtual function. This is the function that will be called as a callback when the broadcaster object needs to broadcast its change. We must override this in order for the code to compile, and for us to use it.

Note
Listener classes often contain other virtual functions that may be overridden, but these are optional as they are needed in fewer cases. See the documentation for the Slider::Listener class for an example.

The pure virtual function in the Button::Listener class is the Button::Listener::buttonClicked() function. We need to add its declaration [2] within our MainContentComponent class as shown here:

MainContentComponent()
{
// ...
}
~MainContentComponent()
{
// ...
}
void resized() override
{
// ...
}
void buttonClicked (Button* button) override // [2]
{
}
// ...

Implementing the listener callback

Now, let's implement the MainContentComponent::buttonClicked() function. Here, we are passed a pointer to the object that has broadcasted the change. We can then compare this pointer with other objects to determine which object it was:

void MainContentComponent::buttonClicked (Button* button)
{
if (button == &checkTheTimeButton) // [3]
{
const Time currentTime (Time::getCurrentTime()); // [4]
const bool includeDate = true;
const bool includeTime = true;
const String currentTimeString (currentTime.toString (includeDate, includeTime)); // [5]
timeLabel.setText (currentTimeString, dontSendNotification); // [6]
}
}
  • [3]: Here we compare the pointer passed to the function with the address of our button, to see if it matches. You should do this even if you have only one button, as we do here. It is safe to compare the pointer to the base Button class passed to the function with instances of Button subclasses, such as the TextButton class as shown here.
  • [4]: This uses the Time class to get the current time from the operating system.
  • [5]: This converts the Time object to a readable string. The two bool values allow some customisation of the output (see the documentation for the Time::toString() function for more information).
  • [6]: Here we update the text displayed within the label.
Note
The dontSendNotification argument [7] prevents the label from broadcasting this change to its listeners, should it have any. (Label objects can have listeners since they can be used to edit text, too.) In this case we know that it can't have any listeners (as it is our own private member) but it is good practice to be specific.

Registering as a listener with the broadcaster

In order to receive the messages that are broadcast, we need to register our listener object with one or more broadcaster objects. In this case, we need to register with the TextButton object. Typically, this would be done within the constructor of the listener subclass [7]:

MainContentComponent()
{
addAndMakeVisible (checkTheTimeButton);
checkTheTimeButton.setButtonText ("Check the time...");
checkTheTimeButton.addListener (this); // [7]
// ...
Note
Most broadcaster objects have an addListener() function for this purpose (the ChangeBroadcaster object is an exception, it has the ChangeBroadcaster::addChangeListener() function instead).

Deregistering as a listener with the broadcaster

Broadcasters will also have a removeListener() function, too. For example, see the Button::removeListener() function. Since our button is owned by the same class that is performing the listening we don't really need to remove the listener as the button will be destroyed at the same time as the listener. For completeness, we could add this to our destructor:

MainContentComponent::~MainContentComponent()
{
checkTheTimeButton.removeListener (this);
}
Warning
Removing listeners appropriately is important if you set up more complex broadcaster-listener systems.

Build and run the application now. When you click the button it should display the time within the label.

tutorial_listeners_and_broadcasters_screenshot2.png
Using the button to display the current time.
Note
The completed code for this section can be found in the MainComponent_02.h file within the Source directory of the demo project for this tutorial.
Exercise
Try changing the format of the text displayed. You can do this by changing the arguments passed to the Time::toString() function. You could also change the code such that the label displays the number of milliseconds between button clicks, rather than the absolute time.

Notes

An example implementation of the code for the final exercise can be found in the files MainComponent_03.h and MainComponent_03.cpp within the Source directory of the demo project for this tutorial.

Summary

In this tutorial we have introduced the basics of the broadcaster-listener system in JUCE. While we have focused on buttons in this tutorial, the same techniques can be applied to many areas of JUCE code. In particular we have learned:

  • How to make one of our custom classes a listener-type object.
  • How to add the listener callback function.
  • How to register and deregister as a listener with a broadcaster object.
  • How to access the current time using the Time class.

See also