How QT signals with parameters works - c++

can someone tell me how exactly signals with parameters work? I mean... if i have declared signal f.e.:
void sign1(int)
how should i specify what integer i want to send with that signal? Also, can i declare signal with multiple arguments? Like:
void sign2(int, int)
And again... i want to send with sign2 two out of four variables that i have. Is that possible, and how it should be done? To specify my question below is a little more detailed example:
class Board
{
signals:
void clicked(int, int);
private:
int x1{4}; int x2{4}; int x3{5}; int x4{8};
}
and there is board.ui file with pushbutton. After pushbutton is clicked i want to send to the slot for example x1 and x3. Example:
connect(ui->button, SIGNAL(clicked(int, int)), obj2, slot2);
I hope that it's somehow clear. I will really appreciate your help.

QObject::connect() works like this (in the general case, not using lambda):
connect(obj1, obj1_signal, obj2, ob2_slot)
Since there is no signal clicked(int, int) in the class QPushButton (which I assume you are using), it cannot be use for the connection.
If you want to have the signal clicked(int, int) in a button, you can subclass QPushButton, add the signal, and using emit to send the signal where the click event is handled.
However, that is not a good design, since you will have to store a Board object (or at least a reference to it) in the button class, which is irrelevant to the class.
Instead, you can have a slot Board::buttonClicked(), connected to QPushButton::clicked(bool). Then in that slot, you can do emit Board::clicked(int, int).

The rule for signal/slot connection may be formulated as the following:
You can ignore signal arguments, and you cannot create slot arguments from nothing
What does it mean?
If your signal has n arguments, your slot shall have at most n arguments as well (beware of the types). See tabular below.
On first line, you have a signal with two arguments, thus, your slot can have two arguments (using all the signal arguments), or one argument (ignoring one argument of the signal) or no argument (ignoring both signal arguments)
On the second line, you have a signal valueChanged(int) with one argument. Your slot may have one or no argument (ignoring the signal argument) but may not have two
or more arguments as you cannot create values.
On the third line the signal textChanged(QString) cannot be connected with setValue(int) because we cannot create an int value from from QString.
The fourth line follow these rules. If the signal has no argument, the connected signal cannot create new arguments, thus update() is correct, setValue(int) isn't.
Another point that shall be looked at is overloading of signal/slots. It is the case where many signals/slots with the same name but with different numers or different types of arguments.
You may have the class QLCDNumber, where the slot display has many overload. In this cas, you have to explicitly defined which pair of signals slots, you would like to use as explained here
You can try out the following example:
Example :
#include <QtWidgets>
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QWidget *window = new QWidget();
window->setAttribute(Qt::WA_DeleteOnClose);
QVBoxLayout *topLayout = new QVBoxLayout(window);
//Set up of GUI
QSlider *slider = new QSlider(Qt::Horizontal);
slider->setRange(0, 100);
QSpinBox *spin = new QSpinBox;
spin->setReadOnly( true );
QHBoxLayout *horizontalLayout = new QHBoxLayout;
horizontalLayout->addWidget(slider);
horizontalLayout->addWidget(spin);
topLayout->addLayout(horizontalLayout);
// using pointer to member function
QObject::connect(slider, &QSlider::valueChanged,
spin, &QSpinBox::setValue);
// set the slider position and hence the QSpinBox value too
slider->setValue(40);
// Uncommenting the following connect will result in a compile time error.
// The signal passes no arguments whereas the slot is expecting a single
// argument.
// By using function pointers we get compile time parameter list checking.
// Using the old-style SIGNAL/SLOT macros this would have been detected
// as a run time warning only.
//QObject::connect(slider, &QSlider::sliderPressed,
// spin, &QSpinBox::setValue);
QTextEdit *textEdit = new QTextEdit();
textEdit->setAttribute(Qt::WA_DeleteOnClose);
// Uncommenting the following connect will result in a compile time error.
// The signal is passing an incompatible parameter to the slot.
// By using function pointers we get compile time parameter type conversion.
// Using the old-style SIGNAL/SLOT macros this would have been detected
// as a run time warning only.
//QObject::connect(slider, &QSlider::sliderMoved,
// textEdit, &QTextEdit::setFontFamily);
window->show();
return app.exec();
}

Related

Qt InvokeMethod on TextEdit setPalette

Similar to C# I have used a working snippet such as:
QString text = "Hello";
QMetaObject::invokeMethod(m_ui.textEdit_ConnectionStatus, "setText", Qt:QueuedConnection, Q_ARG(QString, text));
... in order to change GUI elements not from the main thread. However, it does not appear to be working in the same way with setPalette for the textEdit.
With:
QPalette pal = palette();
pal.setColor(QPalette::Base, Qt:darkGreen);
QMetaObject::invokeMethod(m_ui.textEdit_ConnectionStatus, "setPalette", Qt:QueuedConnection, Q_ARG(const QPalette&, pal));
How does one go about to change the color of this gui element from another thread?
Edit1:
I forgot to mention the output spits out:
"QMetaObject::invokeMethod No such Method QTextEdit:setpalette(const QPalette&)"
The OP used the QMetaObject::invokeMethod() which relies on registered slots (as they were usual in Qt4). QTextEdit::setText() is such a slot but QTextEdit::setPalette() is not.
Hence, the QTextEdit::setPalette() cannot be found at runtime by its name given as string.
With Qt5, the signal-slot concept was extended to support the connection of signals and slots with compile-time checking.
Out of curiosity, I had a look into the doc. and found QMetaObject::invokeMethod() which accepts a functor:
template <typename Functor, typename FunctorReturnType> bool QMetaObject::invokeMethod(QObject *context, Functor function, Qt::ConnectionType type = Qt::AutoConnection, FunctorReturnType *ret = nullptr)
This is an overloaded function.
Invokes the function in the event loop of context. function can be a functor or a pointer to a member function. Returns true if the function could be invoked. Returns false if there is no such function or the parameters did not match. The return value of the function call is placed in ret.
Note: This function is thread-safe.
This function was introduced in Qt 5.10.
Thereby, I would like to emphasize Note: This function is thread-safe.
So, I made an MCVE to check this out:
// standard C++ header:
#include <chrono>
#include <thread>
// Qt header:
#include <QtWidgets>
// main application
int main(int argc, char **argv)
{
qDebug() << "Qt Version:" << QT_VERSION_STR;
QApplication app(argc, argv);
// setup GUI
QTextEdit qTextEdit(QString(
"<p>Hello world.</p>"
"<p>Hello Qt.</p>"
"<p>Hello Stack Overflow.</p>"));
qTextEdit.show();
// a separate thread to manipulate qTextEdit
std::thread threadPal([&qTextEdit]() {
using namespace std::chrono_literals;
const QColor qColors[] = { Qt::red, Qt::green, Qt::blue, Qt::white };
QColor qColor;
for (const QColor &qColor_ : qColors) {
std::this_thread::sleep_for(1s);
qColor = qColor_;
QMetaObject::invokeMethod(&qTextEdit, [&qTextEdit, qColor]() {
QPalette qPal = qTextEdit.palette();
qPal.setColor(QPalette::Base, qColor);
qTextEdit.setPalette(qPal);
});
}
});
// runtime loop
const int ret = app.exec();
// done
threadPal.join();
return ret;
}
Output:
Please, note that I (carefully) did every access to qTextEdit exclusively
either in the main thread
or inside the lambda which is passed to QMetaObject::invokeMethod().
Qt widgets are by default not thread-safe. So, I have to ensure that the accesses to widgets happen in the GUI thread only (or had to be appropriately guarded).
The reference of qTextEdit is captured in the functor of threadPal. It is used to provide the address of qTextEdit to QMetaObject::invokeMethod() as context. That's necessary to make QMetaObject::invokeMethod() aware that the provided functor has to be executed in a different thread (the GUI thread to which qTextEdit is associated to). (In opposition to qTextEdit itself, the address of qTextEdit is immutable while the thread is running. Hence, an unguarded access is thread-safe.)
As per Qt Documentation, invokeMethod() invokes the member (a signal or a slot name) on the object.
Since in the first case, setText() is a slot of QTextEdit and hence it works perfect.
However in the second case, setPalette() is neither a signal nor a slot and hence you get "QMetaObject::invokeMethod No such Method QTextEdit:setpalette(const QPalette&)" as output.
Moreover it returns false if there is no such member (a signal or a slot name) or the parameters did not match.
see here
If you're using Qt 5.10 or later, you can call invokeMethod on any invokable, such as a lambda:
QPalette pal = palette();
pal.setColor(QPalette::Base, Qt:darkGreen);
auto setPalette = [this, pal] { m_ui.textEdit_ConnectionStatus->setPalette(pal); };
QMetaObject::invokeMethod(m_ui.textEdit_ConnectionStatus, setPalette, Qt:QueuedConnection);

enum values in a class - Qt Creator

I made a functions class that contains enumerated values but when called in my main.cpp, I get a "has not been declared" error. I'm trying to figure out where I'm going wrong but not having much luck. My class header, currently (reduced to fit here):
class main_funcs : public QObject
{
Q_OBJECT
public:
main_funcs(QObject *parent, QQuickView *view)
: QObject(parent), myDialog(view){
IP_is_set = false;
newIP = null;
newIP.resize(50);
local_IPv4 = null;
enum direction {up, down};
enum sys_sides {left, right};
enum sys_control {analog, digital};
public slots:
void myfunc1();
void myfunc2(sys_sides side);
void myfunc3(direction dir);
void myfunc4(sys_control type);
private:
...
...
}
and in my main.cpp, I'm connecting signals to slots:
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
// MAIN CONTROL WINDOW:
QQuickView* view = new QQuickView(QUrl("main.qml"));
view->show();
QQuickItem *rect = view->rootObject();
main_funcs *funcs = new main_funcs(0, view);
QObject::connect(rect, SIGNAL(onClicked_func1()), funcs, SLOT(myfunc1()));
QObject::connect(rect, SIGNAL(onClicked_func2()), funcs, SLOT(myfunc2(funcs::up)));
QObject::connect(rect, SIGNAL(onClicked_func3()), funcs, SLOT(myfunc3(funcs::left)));
QObject::connect(rect, SIGNAL(onClicked_func4()), funcs, SLOT(myfunc4(funcs::analog)));
The error appears in the class header at the functions that require enumerated values. The error is that the enumerated value "has not been declared" -It's declared in the public heading just above it. I'm still a C/C++ newb, and definitely new to Qt as I haven't done much with it. Can anyone point me in the right direction at the very least? Thanks!
Your call to your enums is wrong. They are scoped by the class, not the class object.
Try this:
QObject::connect(rect, SIGNAL(onClicked_func2()), funcs, SLOT(myfunc2(main_funcs::up)));
QObject::connect(rect, SIGNAL(onClicked_func3()), funcs, SLOT(myfunc3(main_funcs::left)));
QObject::connect(rect, SIGNAL(onClicked_func4()), funcs, SLOT(myfunc4(main_funcs::analog)));
EDIT:
That'll solve the "has not been declared" error, but I realize that you'll then get another error. You're trying to pass arguments into a function pointer, that won't work.
A signal will take an argument, the parameter that you pass in there will be passed, by Qt, to your slot. You do not control the arguments to your slot in the connection. You control your arguments to the slot by what you pass to the signal.
Read through this for more info: http://doc.qt.io/qt-5/signalsandslots.html
There are some major issues with your code. You should not define values when connecting signal and slots. Only the types should be provided in the old style connecting syntax.
Also when connecting a signal to some slot, the signal should provide the arguments for the slots. So here you cal not connect any of the signals onClicked_func2,.. to the slots as they do not have any argument apparently. (In case onClicked_func2 is really a signal, it looks like a slot)
Also if you want to use the enumeration types, you should use the enumeration in the class scope by following the class name like :
main_funcs::up
First of all, I suggest to put constructor definition in the cpp file, not in the class definition file, it will help keeping things tidy and not to forget closing curly brackets of the constructor like in this case.
Second I suggest also to keep your enums outside the class definition so you will be able to use them in other classes, as they seem to be very general logic enums.
Thirds signals and slots do not work that way, you need to connect SIGNAL signatures with identical slot signatures you cannot be passing actual parameters during connect I do not thing that will ever work, and you should be getting also a lot of connect errors during start up.

QMenu cannot read function Slot

My intent is create a context menu to copy the cell content to the clipboard. With the help of sender() I’m able to connect the same function to two different QTableWidget. Everything works, except for this error message:
"QObject::connect: Incompatible sender/receiver arguments
QAction::triggered(bool) --> MainWindow::copyToClipboard(QTableWidget*,int,int)"
This is the part of code that generates the error
void MainWindow::ProvideContextMenu(const QPoint& pos) // this is a slot
{
QTableWidget *tw = (QTableWidget *)sender();
int row = tw->currentRow();
int col = tw->currentColumn();
QMenu menu;
menu.addAction(QString("Test Item"), this,
SLOT(copyToClipboard(QTableWidget *, int,int)));
menu.exec(tw->mapToGlobal(pos));
}
void MainWindow::copyToClipboard(QTableWidget *tw, int row, int col) {
clipboard = QApplication::clipboard();
clipboard->setText(tw->item(row, col)->text());
}
I've been looking in the official documentation for hours, but found nothing about this. There is a solution?
From the documentation:
The signals and slots mechanism is type safe: 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.) Since the signatures are compatible, the compiler can help us detect type mismatches when using the function pointer-based syntax. The string-based SIGNAL and SLOT syntax will detect type mismatches at runtime.
This is the culsprit:
menu.addAction(QString("Test Item"), this,
SLOT(copyToClipboard(QTableWidget *, int,int)));
You cannot have non-matching signal-slot parameters like that. You can only connect slots that have no parameters or one boolean to the triggered(bool) signal. You have to reconsider your design.

Connect all of an objects signals to a single slot

Been looking around a bit and people seem to have a similar issue but with multiple signals from different sources etc. My situation is that I have a object that signals if it succeded, failed or got canceled. These signals are passed along to another class and are'nt connected to a slot, just a signal. Now I would like to fix so that no matter what signal the object sends (failed, succeded, canceled) a slot will be called that will delete the object. In short, I want a way of connecting every signal of an object to a slot.
Want to do something like this:
connect(myObject, allSignals, this, handleObjectDone());
void handleObjectDone() {
myObject->deleteLater();
}
Is there any way of doing this? Or should I just pass two signals everytime it does something, for example emit readyToBeDeleted() and emit succeded()?
Thanks!
Setting aside any qualms about whether connecting all the signals in one object to a single slot in another object is actually a wise thing to do, below is a function that does that, along with a unit test to verify that it works.
If you watch stdout while you run this, you will see it print out all the connections it is making. When it runs, clicking on the QLineEdit will cause the QLineEdit to emit a signal, which will (of course) cause the QApplication::quit() slot to be called, so the application will exit.
#include <stdio.h>
#include <QApplication>
#include <QLineEdit>
#include <QMetaMethod>
#include <QMetaObject>
void ConnectAllSignalsToSlot(QObject * sourceObject, QObject * targetObject, const char * slotName)
{
const QMetaObject * mo = sourceObject->metaObject();
if (mo)
{
int numMethods = mo->methodCount();
int firstMethod = mo->methodOffset(); // set to 0 instead if you want to connect signals from superclasses too
for (int i=firstMethod; i<numMethods; i++)
{
QMetaMethod mm = mo->method(i);
if (mm.methodType() == QMetaMethod::Signal)
{
QString signalString = QString("2") + mm.signature();
printf("Connecting up signal [%s] on object %p to slot [%s] on object %p\n", signalString.toUtf8().constData(), sourceObject, slotName, targetObject); // just so we can see what it's doing
QObject::connect(sourceObject, signalString.toUtf8().constData(), targetObject, slotName);
}
}
}
else printf("Error, sourceObject has no QMetaObject????\n");
}
int main(int argc, char ** argv)
{
QApplication app(argc, argv);
QWidget * testSource = new QLineEdit;
testSource->show();
ConnectAllSignalsToSlot(testSource, &app, SLOT(quit()));
return app.exec();
}
You can connect any number of signals to any number of slots (as well as other signals). It makes perfect sense to connect the signals to two slots for this purpose. The slots are called in the order they are connected. Emitting two signals consecutively is perfectly reasonable as well. Of course readyToBeDeleted() should be emitted after succeeded() so that the object isn't deleted before emitting its result signal.
Unless I'm misunderstanding you, it's that simple.

Qt issue passing arguments to slot

I can't seem to pass an argument to a slot. If I don't pass an argument, the function rolls through fine. If I pass an argument (integer), I get the errors "No such name type" and "No such slot" when I compile.
In my header, I declare:
private slots:
void addButton(int);
signals:
void clicked(int)
in my Main.cpp, I do:
int count;
int count = 0;
QPushButton* button = new QPushButton("Button");
_layout->addWidget(button);
connect(button, SIGNAL(clicked(count), this, SLOT(addButton(count)));
....
void Main::addButton(int count) {
//do stuff with count
}
Sebastian is correct that you cannot do this in the way you're trying, however Qt does provide a class that gives you the functionality you want.
Check out the QSignalMapper. It allows you to associate an integer with an object/signal pair. You then connect to its signals instead of directly to the button.
The signal and the slot must have the same number and type(s) of argument(s), and you can only pass the argument(s) of the signal to the slot, not any variable or value that you want.
I can see three problems with this.
Firstly, the clicked() signal is emitted by QPushButton (with no parameters), but you're trying to redefine it in your own class (with an int parameter). If you want to do this:
SignalClass* objectWithSignals = new SignalClass;
SlotClass* objectWithSlots = new SlotClass;
connect(objectWithSignals, SIGNAL(a()), objectWithSlots, SLOT(b()));
then you can only connect to the signals already defined in SignalClass. In other words, the signal a() must belong to SignalClass, not SlotClass.
(In fact, clicked() is defined in QPushButton's base class QAbstractButton.)
Secondly, inside the connect() function, you need to specify the signal and slot signatures with their parameter types. Where you have count inside the connect() function, it should be int.
And thirdly, there's a bracket missing in your call to connect: SIGNAL(clicked(count)).
Hope that helps.