1
votes

I'm trying to connect the signal currentIndexChanged(int) from QComboBox to a slot from my class that receive an enum, like Foo::mySlot(EnumFoo).

I know that all these ways DON'T work:

  1. connect(cb, &QComboBox::currentIndexChanged, foo, &Foo::mySlot);
  2. connect(cb, static_cast<void (QComboBox::*)(EnumFoo)>(&QComboBox::currentIndexChanged), foo, &Foo::mySlot);
  3. connect(cb, &QComboBox::currentIndexChanged, foo, static_cast<void (Foo::*)(int)>(&Foo::mySlot));

because in C/C++, int never cast implicitly to an enum type. The opposite is true, I think if my signal has an enum parameter, I can connect it to a slot with int parameter without problem.

I know how to solve this question using lambda function:

connect(cb, &QComboBox::currentIndexChanged, [=](int i){ foo.mySlot(static_cast<EnumFoo>(i)); });

Is there any way to solve this problem WITHOUT lambda function?

EDIT: Using the solution I proposed, I'm connecting Foo::mySlot this way.

LambdaWrapper *lw = new LambdaWrapper;
lw->connect(cb, static_cast<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged), [=](int i){ foo.mySlot(static_cast<EnumFoo>(i)); }, foo);

and I don't have to worry about disconnection stuff anymore. Just have to manage lw lifespan.

2
There are numerous ways to avoid the lambda function, of which there is write your own enum containing class, provide a "wrapper" signal/slot that just forwards its argument with a static_cast and does exactly the same as your lambda.... Are you offended by the lambda (I'm sure it didn't mean it that way), are you used to not-so-strongly types languages, or do you have the misconception that this is going to ruin ~performance~?rubenvb
No, it's just curiosity. Indeed, I have a little doubt: I don't understand well if and in which situations we should release the QMetaObject::Connection before destruct the QComboBox or the Foo class using QObject::disconnect(const QMetaObject::Connection&) overloaded member. Or if we can just ignore it. If I can ignore this disconnect stuff, no problem using lambdas. On the other hand if I have to deal with it, maybe I should use a wrapper slot as pointed by you.Vinícius A. Jorge

2 Answers

2
votes

Use an intermediate slot that takes an int and calls the other slot function directly.

class Foo : public QObject
{
    Q_OBJECT:

public slots:
    void IndexChanged(int intParam);    
    void mySlot(EnumFoo fooType);
};    

void Foo::IndexChanged(int index);
{
    EnumFoo fooType = <static_cast<EnumFoo>(index);
    mySlot(fooType);
}

connect(cb, &QComboBox::currentIndexChanged, foo, &Foo::IndexChanged);
0
votes

I've created a small class to automate this disconnect stuff handling.

lambdawrapper.h

class LambdaWrapper : public QObject {
    Q_OBJECT
public:
    explicit LambdaWrapper(QObject* parent = 0);
    virtual ~LambdaWrapper();

    template<typename Func1, typename Func2>
    void connect(const typename QtPrivate::FunctionPointer<Func1>::Object *sender,
                 Func1 signal,
                 Func2 slot,
                 QObject* receiver = 0) {
        connections << QObject::connect(sender, signal, slot);
        helperConnection(receiver);
    }

private slots:
    void disconnectReceiver(QObject* obj);

private:
    void helperConnection(QObject* obj);
    QList<QMetaObject::Connection> connections;
    QObjectList receivers;
};

lambdawrapper.cpp

LambdaWrapper::LambdaWrapper(QObject *parent) : QObject(parent) { }

LambdaWrapper::~LambdaWrapper() {
   for (const QMetaObject::Connection& c : connections)
       QObject::disconnect(c);
}

void LambdaWrapper::disconnectReceiver(QObject *obj) {
    if (receivers.contains(obj)) {
        QList<const QMetaObject::Connection*> toRemove;
        const int n = receivers.size();
        for (int i=0; i<n ; ++i) {
            if (receivers.at(i) == obj) {
                disconnect(connections.at(i));
                toRemove << & connections.at(i);
            }
        }

        receivers.removeAll(obj);
        for (const QMetaObject::Connection* c : toRemove)
            connections.removeAll(*c);
    }
}

void LambdaWrapper::helperConnection(QObject* receiver) {
    receivers << receiver;
    if (receiver) QObject::connect(receiver, &QObject::destroyed, this, &LambdaWrapper::disconnectReceiver);
}

So, all you have to do is to instantiate this class into your main QWidget, QMainWindow or QDialog derivative.

foo.h
....
class Foo : public QMainWindow
....
private:
    LambdaWrapper* lw;
....

foo.cpp
.... 
lw = new LambdaWrapper(this);
QCheckBox *ck = new QCheckBox("check");
lw->connect(ck, &QAbstraceButton::toggled, [=](bool b){ /* do stuff */}, 0);
....

The 4th argument of connect member is optional and represents some QObject pointer which is called inside the lambda (and that can crash the program if the lambda is called with this object already deleted). If you pass this argument, its destroyed signal will be connected to a slot in LambdaWrapper which ensures that all connections to this receiver will be disconnected when this receiver is destroyed.

It's pretty dirty but solves my problem. Now I can connect to lambdas straightforward and don't have to worry about disconnecting them.