10
votes

I basically have multiple events signals which I want to connect to the same slot. What I want to know is how can I pass string based parameters to that same slot so that the slot knows which is this signal coming from. One alternative is to make as many slots as there are signals and then connect them in a 1:1 manner, but this is efficient, considering that the code for all the processing is very similar. I tried doing this but I'm getting some errors:

connect(selecter1,SIGNAL(selected(QString)),this,SLOT(backgroundTypeChoiceMade(QString)));
connect(button1,SIGNAL(clicked()),this,SLOT(backgroundTypeChoiceMade("button1")));
connect(button2,SIGNAL(clicked()),this,SLOT(backgroundTypeChoiceMade("button2")));

The error is related to the parameters I'm passing in the last 2 commands .. And backgroundTypeChoiceMade is declared like this:

void backgroundTypeChoiceMade(QString);

Can someone tell me what the error is in the above code ?

6

6 Answers

9
votes

You can use QSignalMapper. Although the QSignalMapper is the answer to your question, I think jon hanson's answer is the way you should take. You get much more cleaner code that way.

8
votes

Four methods. One doesn't suck.

  1. QSignalMapper. Works, but makes for messy code.
  2. Named slots. Messy for any significant number of senders, and doesn't work for dynamically-generated senders (e.g., buttons in a list).
  3. sender()-compare. Can handle dynamic senders, but is still kinda ugly.
  4. Subclass the sender. Doesn't suck. Gives you what you really wanted all along: parameterized signals.

Especially when you're using a small number of signals and sender types and when the senders are dynamically generated, subclassing the sender is the cleanest way. This lets you overload the existing signals to contain whatever parameters you need.

And now, wiring up the signals and slots just works:

Keypad::Keypad(QWidget *parent) : QWidget(parent)
{
    for (int i = 0; i < 10; ++i)
    {
        // KeypadButton keeps track of the identifier you give it
        buttons[i] = new KeypadButton(i, this);
        // And passes it as a signal parameter. Booyah.
        connect(buttons[i], SIGNAL(clicked(int)), this, SIGNAL(digitClicked(int)));
    }
    createLayout();
}

void Keypad::digitClicked(int digit)
{
    // The slot can find the clicked button with ease:
    dial(button[i]); // or whatever
    //...
}

and the extra code is out-of-sight in a subclass you'll never have to touch again.

See http://doc.qt.digia.com/qq/qq10-signalmapper.html#thesubclassapproach for an example implementation of subclassing QPushButton to emit clicked(int) signals. Also discusses all four methods - named slots ("the trivial solution"), sender(), subclassing, and signal mapper.

Caveat: Obviously works best for small numbers of sender types. But that's usually the case. And in that case, it's worth it.

5
votes

What is inefficient about using separate slots? If there's commonality in the slot handlers then move that into a function, e.g. extending ereOn's example:

void YourClass::YourClass() :
  m_button1(new QPushButton()),
  m_button2(new QPushButton())
{
  connect(m_button1, SIGNAL(clicked()), this, SLOT(yourSlot1()));
  connect(m_button2, SIGNAL(clicked()), this, SLOT(yourSlot2()));
}

void YourClass::common(int n)
{
}

void YourClass::yourSlot1()
{
    common (1);
}

void YourClass::yourSlot2()
{
    common (2);
}
4
votes

You can't pass constants to connect() because the effective parameters are deduced at execution time, not compile time.

However, while this is against the OO principle, you can use QObject::sender() which gives a pointer to the emitter QObject.

Example below:

void YourClass::YourClass() :
  m_button1(new QPushButton()),
  m_button2(new QPushButton())
{
  connect(m_button1, SIGNAL(clicked()), this, SLOT(yourSlot()));
  connect(m_button2, SIGNAL(clicked()), this, SLOT(yourSlot()));
}

void YourClass::yourSlot()
{
  if ((QPushButton* button = dynamic_cast<QPushButton*>(sender()))
  {
    // Now button points to a QPushButton* that you can compare with the pointers you already have

    if (button == m_button1)
    {
      // Whatever
    } else
    if (button == m_button2)
    {
      // Whatever
    }
  }
}

If you have many buttons, you may also use a QSignalMapper by providing an identifier for each button.

3
votes

You can now really bind a value when connecting. Qt5 added support for that.

Example:

connect(sender, &Sender::valueChanged,
    tr1::bind(receiver, &Receiver::updateValue, "senderValue", tr1::placeholder::_1));

See more info.

NB: you can of course use std::bind or boost::bind instead of tr1::bind.

0
votes

If you really don't want to use QSignalMapper, you could do something like this:

class SignalForwarderWithString: public QObject
{
    Q_OBJECT
public:
    SignalForwarderWithString(QString data = "", QObject *parent = 0) : QObject(parent), _data(data) {}
    QString _data;
signals:
    void forward(QString);
public slots:
    void receive() { emit forward(_data); }
};

...
connect(selecter1,SIGNAL(selected(QString)),this,SLOT(backgroundTypeChoiceMade(QString)));

SignalForwarderWithString *sfws;
sfws = new SignalForwarderWithString("button1", this);
connect(button1,SIGNAL(clicked()), sfws, SLOT(receive(QString)));
connect(sfws, SIGNAL(forward(QString)), this,SLOT(backgroundTypeChoiceMade(QString)));

sfws = new SignalForwarderWithString("button2", this);
connect(button2,SIGNAL(clicked()), sfws, SLOT(receive(QString)));
connect(sfws, SIGNAL(forward(QString)), this,SLOT(backgroundTypeChoiceMade(QString)));

but QSignalMapper is just as easy...

QSignalMapper *mapper = new QSignalMapper(this);
connect(button1, SIGNAL(clicked()), mapper, SLOT(map()));
mapper->setMapping(button1, "button 1");
connect(button2, SIGNAL(clicked()), mapper, SLOT(map()));
mapper->setMapping(button2, "button 2");
// you might have to tweak the argument type for your slot...
connect(mapper, SIGNAL(mapped(const QString &), this, SLOT(backgroundTypeChoiceMade(QString)));