Juce uses broadcasters and listeners pattern to propagate state changes. This is like classical observer pattern. Broadcasters are subjects and listeners are observers. Juce Framework provides predefined library to automate this process.
Let’s say we have a Window with a slider and a label. When the user changes the slider position, the label will show the value.
At this moment, both the slider and the label don’t interact with each other.
We are going to show the value of slider in the label.
Most Juce components have an inner class called Listener.
- This class is an abstract class with at least one pure virtual function. An observer has to inherit from this abstract class and implement the pure virtual function(s). This pure virtual function is the callback.
- one member function called void addListener(Listener* listener). Using this function, an observer can register it self.
- another member function called void removeListener(Listener* listener). This one removes the previously registered listener.
- Whenever a listener is registered, the address is stored in a ListenerList container.
- Whenever the state changes, the broadcaster iterates over all the registered listeners and calls the appropriate callback.
Slider::Listener provides Slider::Listener::sliderValueChanged( Slider *slide). We can override that function in the MainComponent. Then register the main component as a listener. In the implementation of sliderValueChanged, we can set the value of the slider in the label.
The broadcaster and listener can have a many to many relationship. One callback can be invoked by many broadcaster. So, you need to know which instanced invoked it. So we do the address comparison in line 44(In this case, it’s not necessary, because we have only one broadcaster.).
You can also provide your own Listener interface.
Let’s say you have a login form. It has two input boxes, one submit button and one clear button. Clear button is activated when at least one input box has some text and Submit button is activated if both are filled with some data.
We separate the username and password input control to a separate component, called InputForm. In the main component, we have the InputForm and bothe the buttons.
We can add a listener interface to the InputForm. It will have to callbacks
InputForm must maintain a list of interested listeners and should provide api call for registration and de-registration.
Juce ListenerList<T> class should be used to store the list of listeners. It has many advantages than the std lists, like
- add or remove listeners from the list during one of the callbacks — i.e. while it’s in the middle of iterating the listeners, then it’s guaranteed that no listeners will be mistakenly called after they’ve been removed, but it may mean that some of the listeners could be called more than once, or not at all, depending on the list’s order.
- It has a bailout checker(Component::BailOutChecker class) which checks if the component has already been deleted. This provides safety guarantee that no callback will be called on a deleted component.
- ListenerList checks if the listener is already in the list or not. So, double addition won’t have any effect.
Now, after implementing input and clear logic, the InputForm control looks like this.
When you clear the label programmatically, you need to set the NotificationType. In this case, once the input texts are cleared, we need the callbacks to be called to set the submit and active status. So, we use NotificationType as sendNotification.
In the main component, we implement the InputForm::Listener interface and accordingly set the enabled/disabled status of the buttons.
The application finally looks like this
Comparison with Qt Signal/Slots
- Callbacks are like Slots in Qt. Qt MOC does all the major work of adding code for callback handling. There is no MOC here, so developer has to write everything.
- In Qt, signal emitters are completely decoupled from signal receivers. So, if you want to add a new signal, it needs minimum amount of code change. In Juce, you will need at least one pure virtual function, code for listener registration and removal.
- Both Qt and Juce provide asynchronous call by using Qt Event loop and Juce AsyncUpdater respectively. But Juce needs some protection from race condition.
I wrote this blog to clarify my doubts. Feel free to correct if I am wrong or write a comment if you want to suggest something.
The source code is shared here