How to pass a signal as function parameter?

Viewed 4717

So i am looking to make our own generic inherited checkbox class that will be able to take in some values in its constructor and pop out a widget that is fully connected to our model in the manner we need.

Currently we do something like this within our view

connect(checkboxWidget, &QCheckbox::Clicked, this, &VMyView::Signal);

Which emits the Signal from VMyView when the checkbox is clicked.

If i wanted to pass that signal as a parameter into my new inherited class to be hooked up in its own connect statement, how would I do so?

Research has shown me i can pass a const char* but i get compilation errors that the signal/slot do not match.

Example

CheckBox(View myView, const char* signal)
{
    connect(this, &QCheckBox::Clicked, myView, signal);
}

Returns an error that Signal and slot arguments are not compatible. Ive also tried SIGNAL(signal) with the same result.

2 Answers

The solution ended up being fairly simple in the end

Instead of using this from within my View

connect(pCheckbox, &QCheckBox::clicked, this, &MyView::Signal);

I use

    connect(this, &QCheckBox::clicked, View, signal);

Where signal and comes into my function via a function pointer

MyCheckBox::MyCheckBox(QWidget* parent, MyView* View, void(MyView::*signal)(bool))

The key takeaway is

void(MyView::*signal)(bool) 

is equal too

&MyView::Signal

I think the major issue here is that signals are not static member functions. Thus they require a pointer to an instance of the class to be called correctly. So you cannot just pass in things like &VMyView::Signal, as there's no corresponding this pointer attached to the function. (This is why most of the QObject::connect() overloads require an instance to the sender/receiver objects.)

One way to solve this is to create a function object, which contains both the member function pointer and the pointer to the object on which to call it. This can be passed to the QObject::connect() function just fine.

Here's an example:

// objects.h
#include <QtCore>

class Receiver : public QObject
{
    Q_OBJECT
    public:
        Receiver( QObject *parent = nullptr)
            : QObject(parent)
        {
        }

        ~Receiver() { }

    signals:
        void sig(void);
};


class Sender : public QObject
{
    Q_OBJECT
    public:
        Sender(std::function<void(void)> &bound_signal, QObject *parent = nullptr) 
            : QObject(parent)
        {
            // automatically emit `Sender::sig` on a timer, for testing.
            timer = new QTimer(this);
            timer->setInterval(1000);
            QObject::connect(timer, &QTimer::timeout, this, &Sender::sig);
            QObject::connect(this, &Sender::sig, bound_signal);
            timer->start();
        }

        ~Sender() { }

    signals:
        void sig(void);

    private:
        QTimer *timer;
};

And then a main function:

// main.cc
#include <QtCore>

#include "objects.h"

int main(int argc, char *argv[])
{
    QCoreApplication app(argc, argv);
    Receiver receiver; // object to receive the signal

    // Bind the receiver's signal to the instance of the class
    std::function<void(void)> signal = std::bind(&Receiver::sig, &receiver);

    // Create a Sender, which will connect its own signal to the
    // given bound signal
    Sender sender(signal);

    QObject::connect(&receiver, &Receiver::sig,
            []() -> void { qDebug() << "received"; });
    return app.exec();
}

So, in your case, the Receiver and its signal would be replaced by VMyView and the signals you want to chain, and Sender would be the custom checkbox class you've implemented. Then in the constructor of the checkbox class, connect whatever signals you want to the given bound signals. You can also pass in a list of bound signals, e.g., std::list<std::function<void(void)>> &bound_signals.

I have to say, though, I'm not sure what this buys you. You'll need to write the connection logic somewhere, and I don't see why it needs to be in the constructor of the checkbox class. Wherever the checkbox and the VMyView class are created and used, that seems like a better place to put the connection code. It's more obvious, less convoluted, and there's better separation of concerns. The checkbox class shouldn't have to know or care what signals/slots its connected to. The application logic (i.e., where the objects are used) should define how the objects interact with one another.

Related