ActionListener ? Getting callbacks from the elements of an array


#1

Hello
I’m wondering if it’s a good idea:
I would like to get callbacks from the elements inside an array. These elements are components, and their quantity is not fixed. For example, I would like one element in the array to send a message to the parent when it is clicked, or dragged. What would be a best practice ?


#2

I highly suggest having a look into the OwnedArray class and also at change broadcasters and listeners. The OwnedArray will allow you to easily expand and shrink the array size whilst also deleting objects when they are removed or the array is deleted. The change broadcasters alert any classes listening that something has changed (i.e. you started dragging or clicked).
I am currently using this method in a set of window drawing/ management classes where the change broadcaster in the window notifies a listener in an ownedarray for holding a list of open windows that the close button has been pressed and removes the corresponding object from the array when it is clicked


#3

You could also implement a specific listener for yourself: with the aid of the ListenerList class template, it is quite easy to roll your own (see the doc).
You could design the methods in your Listener the way you want, for example receiving an argument which is the pointer to the object which triggered the event, or its index in the array, whatever.

That would be the way I’d go if I were you, unless you have specific needs for sending the notifications asynchronously (i.e. trigger the notification on any thread, receive them always on the GUI thread), but that does not seem the case: the events that you mentioned (clicking, dragging) suggest that all of those are already happening on the GUI thread, so you don’t need the async notification mechanism at all.


#4

Thanks. I found a way of doing it, but I still feel rater guilty: when I get a callback from the element of my array in a parent component, I want to change some values in a related array. What gives me the guilt is I need to go across the whole array, called with a function having the element’s name as one of its args, and check if the element has the same componentID…


#5

Why don’t you just call the parent directly?

static_cast<myComponentClass *>(getParentComponent())->myMovementHandler()

#6

Ah, this is very interesting, I have to try!


#7

Can you tell, what kind of relation? like sender[i] => listener[i]?


#8

Er, I meant answering to Daniel…


#9

Yes, this kind of relation.


#10

In that case I’d go like this: (typing code is faster than to explain, add all the obvious…)

class MyParent : public Component, public Button::Listener {
public:
    MyParent () {
        for (/* bla */) {
            auto* b = senders.add (new Button());
            addAndMakeVisible (b);
            b->addListener (this);
            receivers.add (new MyOther());
        }
    }
    void buttonClicked (Button* b) override {
        int idx = senders.indexOf (b);
        if (isPositiveAndBelow (idx, receivers.size()))
            receivers.getUnchecked (idx)->foo();
    }
private:
    OwnedArray<Button> senders;
    OwnedArray<MyOther> receivers;
}

#11

Something that’s often forgotten about Component (and that’s therefore inherited by all other widgets, like Button), is that they come with a NamedValueSet where you can set any sort of property for later usage.

In your case, you could store there the index of the receiver that each button should interact with, like this:

button->getProperties().set ("receiverIndex", i);

and then retrieve it later in your buttonClicked() like this:

int receiverIndex = button->getProperties() ["receiverIndex"];

This saves the need for the “indexOf” search in the array, and also drops the constraint that receivers and buttons must have the same indices in their respective arrays.


#12

Good idea! I forgot indeed.

It just depends on the use-case, that’s why I asked about the relation. If there are items inserted and moved, you have to manually maintain the indices in your scenario. It really depends on the use-case and has no general answer…

EDIT: actually, there is a generic approach, a mapping, probably the fastest to let the map do the lookup:

std::map<Component*, Component::SafePointer<MyReceiver>> mapping;