Qt: Upcasting a signal's parameter to the slot - c++

I have a signal that is emitted w/a parameter taken from a subclass, but in one slot implementation, I don't need a special handler for the subclass; just the parent class. I thought that connect() would upcast that parameter, but unfortunately, I get:
QObject::connect: Incompatible sender/receiver arguments
emitter::mysignal(messageSubclass *) --> slotter::generalHandler(messageSuperclass *)
In emitter I have:
emit mysignal((messageSubclass *)msg_ptr );
There are various places in the code were I need to handle this messageSubclass specifically. But, in one place, a whole family of signals can be handled by just one slot method, so I have:
connect(emitter_ptr, SIGNAL(mysignal(messageSubclass *)), this, SLOT(generalHandler(messageSuperclass *)));
I really don't want to have to have generalHandler() be replaced by a couple of dozen specific handlers, that all do the same thing. Putting it another way, I'm trying to avoid something tedious like:
connect(emitter_ptr, SIGNAL(mysignal(messageSubclass *)), this, SLOT(specificHandler(messageSubclass *)));
connect(emitter_ptr, SIGNAL(mysignalA(messageSubclassA *)), this, SLOT(specificHandlerA(messageSubclassA *)));
connect(emitter_ptr, SIGNAL(mysignalB(messageSubclassB *)), this, SLOT(specificHandlerB(messageSubclassB *)));
connect(emitter_ptr, SIGNAL(mysignalC(messageSubclassC *)), this, SLOT(specificHandlerC(messageSubclassC *)));
...
void handlerClass::specificHandler(messageSubclass *msg_ptr) {
generalHandler((messageSuperclass *)msg_ptr);
}
void handlerClass::specificHandlerA(messageSubclassA *msg_ptr) {
generalHandler((messageSuperclass *)msg_ptr);
}
void handlerClass::specificHandlerB(messageSubclassB *msg_ptr) {
generalHandler((messageSuperclass *)msg_ptr);
}
void handlerClass::specificHandlerC(messageSubclassC *msg_ptr) {
generalHandler((messageSuperclass *)msg_ptr);
}
And again, I need to have
signals:
mysignal(messageSubclass *);
for other slots which need messageSubclass.
I'm using Qt 4.8

Related

Qt: How to make Incompatible sender/receiver arguments compatible?

Here is the code where I use connect,
connect(myTimer, SIGNAL(timeout()), ui.widget_2, SLOT(paintEvent(QPaintEvent *)));//draw lines
I use it to go to the slot paintEvent(QPaintEvent *) where I can draw some lines. But I have a message while debugging or running the program which is
QObject::connect: Incompatible sender/receiver arguments
QTimer::timeout() --> MapWidget::paintEvent(QPaintEvent*)
mainWindow.cpp
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
ui.setupUi(this);
myTimer = new QTimer(this);
...
connect(myTimer, SIGNAL(timeout()), ui.widget_2, SLOT(paintEvent(QPaintEvent *)));//draw lines
...
}
ui.widget_2 is an instance of mapWidget.
mapWidget.cpp
void MapWidget::paintEvent(QPaintEvent *)
{
QPainter painter(this);
...
}
I know that the arguments of the signal and slot are incompatible, but I don't know how to make them compatible. It seems that the arguments of MapWidget::paintEvent(QPaintEvent*) can't be ignored, nor can I add arguments to timeout().
I'm so confused.
Don’t try to connect to paintEvent(QPaintEvent *); that method isn’t a slot-method, so calling it via a signal wouldn't work even if you were somehow able to get the right arguments passed to it. Instead connect to the update() slot, which will see to it that the paintEvent(QPaintEvent *) method is called from the proper context, ASAP.
OP's specific use-case: Using a QTimer to paint a widget.
Jeremy Friesner's answer is correct for this use case.
General answer to title question, "How to make Incompatible sender/receiver arguments compatible?"
Use the new Qt 5 version of QObject::connect(): https://doc.qt.io/qt-5/signalsandslots-syntaxes.html
The Qt 5 style connect can do implicit conversions:
// OK: The new version of connect() lets the compiler convert int to double
connect(slider, &QSlider::valueChanged,
doubleSpinBox, &QDoubleSpinBox::setValue);
// ERROR: The old version of connect() needs the parameter types to be exactly the same
connect(slider, SIGNAL(valueChanged(int)),
doubleSpinBox, SLOT(setValue(double)));
Furthermore, you can use a lambda expression to connect to a slot whose parameters are truly incompatible with the signal parameters:
// Using QTimer::timeout() to trigger a call to QLabel::setText(const QString&)
connect(timer, &QTimer::timeout, [=] {
label->setText( QTime::currentTime()->toString() );
});

Pass QMetaMethod as parameter to a function that uses the new QObject::connect syntax

I want to kinda build a wrapper around QObject's connect using the new syntax that has type check at compilation time.
The main idea would be: "hey listener, connect this object's slots to my newData signal"
void Listener::_addClient(const QObject *object, const QMetaMethod& slot)
{
connect(this, &Listener::newData, object, slot);
}
And some Client class would simply do:
Listener myListener;
myListener._addClient(this, &Client::mySlot);
This of course does not compile.
Since all Listener objects will be owned by a manager class, I cannot access them directly, so its not possible to do the traditional connect.
How can I build a wrapper for this connect so I can assure the type check at compilation time?
You might use template:
template <typename Receiver, typename Slot>
void Listener::_addClient(const Receiver *object, const Slot& slot)
{
connect(this, &Listener::newData, object, slot);
}
I could not make Jarod42 solution work because I needed access to protected properties and the method needed to be public.
I ended up proceeding as follow
void Listener::_addClient(const QObject *receiver, const char *slot)
{
connect(this, SIGNAL(newData()), receiver, slot);
}
calling like so
Listener myListener;
myListener._addClient(this, SLOT(mySlot()));
It might not be the pretiest way to proceed but it works.

Qt C++ - How to pass data from a worker thread to main thread?

I am trying to perform interthread communication in Qt (C++). I have a worker thread which does some calculations and I want the workerthread to return its results to the main thread when done. I therefor use a connect, I know thanks to debugging, that the signal is successfully being emit but that it is the slot that isn t being executed and I don t understand why.
The relevant pieces of code:
webcamClass::webcamClass(QObject *parent) : QObject(parent)
{
workerThread = new QThread(this);
workerClassObj = new workerClass();
//connect for image
connect(workerClassObj, SIGNAL(mySignal(QPixmap)), this, SLOT(mySlot(QPixmap)));
//connect(&workerClassObj, workerClass::mySignal(QPixmap), this, webcamClass::mySlot(QPixmap));
connect( workerThread, SIGNAL(started()), workerClassObj, SLOT(getImage()) );
workerClassObj->moveToThread(workerThread);
}
void webcamClass:: foo()
{
workerThread->start();
}
void workerClass::getImage()
{
qint64 successFailWrite;
QImage img;
QPixmap pixmap;
... do some stuff with pixmap...
qDebug()<<"going to emit result";
emit mySignal(pixmap);
qDebug()<<"emitted";
}
void webcamClass::mySlot(QPixmap p)
{qDebug()<<"this message should be displayed"; }
The corresponding header files:
class workerClass : public QObject
{
Q_OBJECT
private:
public:
explicit workerClass(QObject *parent = nullptr);
signals:
void mySignal(QPixmap);
};
webcamClass::webcamClass(QObject *parent) : QObject(parent)
{
Q_OBJECT
public:
explicit webcamClass(QObject *parent = nullptr);
public slots:
void mySlot(QPixmap p);
private:
QThread *workerThread;
workerClass *workerClassObj;
};
The code above just outputs:
going to emit result
emitted
but unfortunately doesn t output this message should be displayed.
webcamClass belongs to the parent thread, while workerClass belngs to -you guessed it- the worker thread.
Could someone explain how to setup my connect so that mySlot() gets triggered?
Thanks!
In the code you pasted in pastebin.com/UpPfrNEt you have a getVideoFrame method that uses while (1). If this method is called, it runs all the time and blocks the event loop from handling signals. You can solve it in many ways, I think the best practice will be to replace the while(1) with something else.
If possible, I highly encourage you to use the new Signal Slot syntax:
connect( SOURCEINSTANCE, &CLASS::SIGNAL, TARGETINSTANCE, &CLASS::SLOT );
In your case, that could be:
connect( workerClassObj, &workerClass::mySignal, this, &webcamClass::mySlot );
Specificallyfor your case, if you want to pass Signals and Slots between threads, you have to be careful. First, check the connection type for the connect call, its acutally the last parameter.
connect( workerClassObj, &workerClass::mySignal, this, &webcamClass::mySlot, Qt::QueuedConnection );
For a detailed explanation look here:
http://doc.qt.io/qt-5/signalsandslots.html
If you want to pass custom types, you have to declare them as metatypes first.
Add e.G. this in your constructor:
qRegisterMetaType("MyDataType");
Please make sure, that your custom datatype has a default constructor and be aware that afaik, references cannot be passed across threads.

QT4 no such slot error

I know there are many many questions that are just the same, but none of them helps me:
class Form1 : public QMainWindow {
Q_OBJECT
public:
Form1();
virtual ~Form1();
public slots:
void langChange(const char* lang_label);
private:
Ui::Form1 widget;
void setLangStrings();
};
From1 constructor:
Form1::Form1() {
widget.setupUi(this);
connect(widget.btnL0, SIGNAL(clicked(bool)), this, SLOT(langChange("en")));
connect(widget.btnL1, SIGNAL(clicked(bool)), this, SLOT(langChange("fr")));
setLangStrings();
}
And I also have this langChange function implemented:
void Form1::langChange(const char* lang_label)
{
GL_LANG = lang_label;
setLangStrings();
}
I get this stupid error when the connect function is called:
No such slot Form1::langChange("sl") in Form1.cpp:15
I'm using NetBeans with QDesigner for the UI. I must say this QT4 is very difficult to learn.
You simply can't connect SIGNAL with bool as argument to SLOT with const char* as argument. To do this kind of stuff you have to use QSignalMapper. You have an example how to use it inside documentation. In your case, it's very simple, so you should handle it easly.
The SLOT function must have the same signature than the SIGNAL function
Edit: From the official Qt documentation (http://qt-project.org/doc/qt-4.8/signalsandslots.html):
The signature of a signal must match the signature of the receiving
slot. (In fact a slot may have a shorter signature than the signal it
receives because it can ignore extra arguments.)

Binding arguments to signals/slots

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 ?
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.
Four methods. One doesn't suck.
QSignalMapper. Works, but makes for messy code.
Named slots. Messy for any significant number of senders, and doesn't work for dynamically-generated senders (e.g., buttons in a list).
sender()-compare. Can handle dynamic senders, but is still kinda ugly.
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.
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);
}
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.
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.
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)));