Qt: connecting signals and slots from text - c++

How would I connect a signal and slot of 2 objects when the object names, signals, and slots are all specified in a text file?
Getting the right objects names isn't an issue, since I can easily loop through an array and compare the names to the spot in the file, but there has to be some sort of way where I can return the signal and slot from the file and use it in the connect function, like:
connect(rtnObj1(line),SIGNAL(rtnSignal(line)),rtnObj2(line),SLOT(rtnSlot(line)));
where rtn functions return the object name/signal/slot, and "line" is the current QString line from the file.
The only way I know of is by literally coding in every single combination and comparing QStrings with if statements, but that would be incredibly tedious, as the amount of combinations would be incredibly high.
Note:
Here's a simplified example demonstrating essentially how this issue exists.
Frame 1:
4 QComboBoxes. The first and third hold object names, the second holds signals, the fourth holds slots. Every item is of course a QString within these lists. Hitting a button appends a new line to a file, writing the text selected from each box.
Frame 2: Has already has the required objects. Reading the file, it would match the objects defined in the list against the ones already created, and connect them as the file describes.
It's easy enough to create an object based on the data a file holds, but how would one create/pull a signal and a slot from a file?
Edit:
Unless, is one able to connect like this?
connect(objectA, "", objectB, "");
Because I just found out that my code will compile like that, however whenever I try putting in the slot or signal names I just get an error like:
QObject::connect: Use the SIGNAL macro to bind Tile::clicked

Your problem is easily solvable with one of the following static QObject::connect() method:
QMetaObject::Connection QObject::connect(
const QObject *sender, const QMetaMethod &signal,
const QObject *receiver, const QMetaMethod &method,
Qt::ConnectionType type = Qt::AutoConnection)
First of all, you need pointers to sender and receiver objects. There are several ways how you can store an object pool. I would suggest to keep all objects in QHash:
QHash<QString, QObject *> m_objects; // I use QObject as a generic example
Now, it's possible to find a pointer to any object for connecting in an efficient way.
The next step would be obtaining QMetaMethod objects for sender's signal and receiver's slot from corresponding QMetaObject objects. Use QObject::metaObject() QMetaObject instances.
Here's the complete code of a function which connects two object using only string parameters:
void dynamicConnect(const QString &senderName, const QString &signalName,
const QString &receiverName, const QString &slotName)
{
QObject *emitter = m_objects.value(senderName);
int index = emitter->metaObject()
->indexOfSignal(QMetaObject::normalizedSignature(qPrintable(signalName)));
if (index == -1) {
qWarning("Wrong signal name!");
return;
}
QMetaMethod signal = emitter->metaObject()->method(index);
QObject *receiver = m_objects.value(receiverName);
index = receiver->metaObject()
->indexOfSlot(QMetaObject::normalizedSignature(qPrintable(slotName)));
if (index == -1) {
qWarning("Wrong slot name!");
return;
}
QMetaMethod slot = receiver->metaObject()->method(index);
QObject::connect(emitter, signal, receiver, slot);
}

Related

How QT signals with parameters works

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();
}

Value of spin box, just before it changes

I'm writig a code using qt libraries, in which I need to get the value of a spin box (by a signal) just before it changes.
I've got:
QSpinBox spinBoxWidth:
QSpinBox spinBoxScale;
I want to connect a signal from spinBoxWidth to spinBoxScale, so that the value of SpinBoxScale is always "the Value of SpinBoxWidth after changing" to "its value before changing".
(Scale = width_new/width_old)
I didn't find any slot in Qt which returns the old value of a spin box while changing the value. Can I somehow write a slot for that?
Best Regards
There are two ways of doing this:
Catch the change before it happens and store the old value using the event system (QKeyEvent, QMouseEvent). This is error-prone, as the value of spinBoxWidth can be set manually.
Connect spinBoxWidth's valueChanged(int) signal to a slot and reference the last value it was called with. I recommend this method.
Try something like this:
class MonitoringObject : public QObject
{
Q_OBJECT
int lastValue;
int currentValue;
...
public Q_SLOTS:
void onValueChanged(int newVal)
{
lastValue = currentValue;
currentValue = newVal;
if (lastValue == 0) //catch divide-by-zero
emit ratioChanged(0);
else
emit ratioChanged(currentValue/lastValue);
}
Q_SIGNALS:
void ratioChanged(int);
After your signals are connected, the flow should look like this:
spinBoxWidth emits valueChanged(int)
MonitoringObject::onValueChanged(int) is invoked, does its work and emits ratioChanged(int)
spinBoxScale receives the signal in its setValue(int) slot and sets the appropriate value.
The easiest way is probably a lambda that caches the value of valueChanged for the next call:
auto const width = new QSpinBox();
width->setValue(200);
connect(width, &QSpinBox::valueChanged,
[prev_value = width->value()](int const value) mutable {
auto const scale = double(value) / double(prev_value);
// do stuff
prev_value = value;
});
I believe there is no specific signal to the "value before change" because you can always store it from the previous signal "onValueChanged()" you received.
So the basic idea would be:
First time, receive signal onValueChanged(value) and store the value value_old;
Next time you receive the signal, you can compute you scale!value/value_old;
Then you can send a new signal, or directly modify the object with the new value.
You can derived your own version of QSpinBox including this code or implemented in the class it has to receive the signal. It depends on your architecture.

How to build a generic method to connect the notifySignal of different Q_PROPERTY types to a void slot(QVariant) from the property char * name?

I'm trying to write a method, with two parameters : the Q_PROPERTY name (char *) and the QObject * associated with, that permit to connect the notifySignal (if exists) of the Q_PROPERTY, to a void slot(QVariant), or to a slot dynamically builded which will call a void method(QVariant). The signature of the signal can vary according to the type of the parameter.
How can I achieve that in Qt 5? Maybe it's impossible, but I will not stop searching while I'm not sure of that.
So I think I have 3 solutions:
building dynamically a slot of the exact signature of the signal, from the signal name, and call a method(QVariant) in it, using the old Qt connect way:
connect(sender, SIGNAL (valueChanged(QString,QString)),
receiver, SLOT (updateValue(QString)) );
using the new Qt 5 connect system:
connect(sender, &Sender::valueChanged,receiver, &Receiver::updateValue );
building all slots signatures that can be used with QVariant.
Althougt, I don't know how to build a slot dynamically that call a specified method for the first solution ; I don't know how to retrieve the function pointer from the QMetaMethod of the notifySignal, for the second solution ; maybe the last solution is the best way, and easy to achieve, but it seams a bit extreme.
What do you think about it?
For "building a slot dynamically" there are private APIs in Qt (look for QMetaObjectBuilder or similar classes). However that doesn't solve the problem of the connection.
Note that QObject::connect is overloaded to take QMetaMethods as signals and slots. So you can easily build a "receiver" class object (for the only purpose of remembering which property it's acting upon):
class Receiver : public QObject {
Q_OBJECT
public:
explicit Receiver(const char *property, QObject *parent = 0)
: QObject(parent),
m_property(property)
{}
signals:
void propertyChangedForObject(const char *property, QObject *object);
public slots:
void onPropertyChanged() {
emit propertyChangedForObject(m_property, sender());
}
private:
const char * const m_property;
};
And hook it up in your method.

How to force Qt to update GUI from not main thread

I'm fighing since last week with problem caused by update of QPlainTextEdit. I'm trying to create separate from QMainWindow Dialog window with QPlainTextEdit inside. The problem begins when I try to use appendHtml signal (also tried with appendText), text that is placed is not visible unless marked by by mouse. Repainting or updating cause in program crash or no visible action.
Simplified code of QDialog with QPlainTextEdit header:
namespace Ui {
class LogWindow;
}
class LogWriter: public QDialog
{
Q_OBJECT
QMutex print_lock;
public:
class Log{
Q_OBJECT
const static int MAX_SIZE = 100;
bool to_terminal;
QString color;
QMutex *print_lock;
QPlainTextEdit *text_place;
QVector< QPair<QString,time_t> > history;
LogWriter * obj;
public:
bool print;
Log(bool _print,QString _color,LogWriter *obj_ = NULL)
{print = _print; color = _color; obj = obj_;}
void setLock(QMutex *print_lock_){print_lock = print_lock_;}
void setTextField(QPlainTextEdit *_text) {text_place = _text;}
Log& operator<<(QString &a);
Log& operator<<(const char* a);
};
static LogWriter* getInstance()
{
static LogWriter instance; // Guaranteed to be destroyed.
// Instantiated on first use.
return &instance;
}
~LogWriter();
Log LOW,MEDIUM,HIGH;
Ui::LogWindow *ui;
signals:
void signalLogAppend(QString);
};
Simplified code of methods definitions:
LogWriter::LogWriter(QWidget * parent): QDialog(parent) {
ui = new Ui::LogWindow;
ui->setupUi(this);
LOW.setLock(&print_lock);
MEDIUM.setLock(&print_lock);
HIGH.setLock(&print_lock);
connect(this,SIGNAL(signalLogAppend(QString)),ui->plainTextEdit,
SLOT(appendHtml(QString)),Qt::DirectConnection);
}
LogWriter::Log& LogWriter::Log::operator<< (QString &s){
history.push_front(qMakePair(s,time(NULL)));
if(history.size() > MAX_SIZE) history.pop_back();
if(print){
//print_lock->lock();
QString text = "<font color=\"";
text += color + "\">";
text += s + "</font>";
//cout << text.toStdString() << endl;
//text_place->appendHtml(text);
//text_place->repaint();
emit (obj)->signalLogAppend(text);
//print_lock->unlock();
}
return *this;
}
I have two separate ui files (first for main window, second for log window).
I have to use log window all across my program (something about 10 threads), and I stucked on this issue. My question is - is it possible to force GUI update without using main thread and if not - what else possibilities I have. If possible I would rather avoid reconstructing all my code - it would take me some time to do it. Right now logging is super easy - I ony need:
LogWindow *log = LogWindow::getInstance();
log->MEDIUM << "something";
As additional info I add QTCreator warning:
QObject::connect: Cannot queue arguments of type 'QTextBlock'
(Make sure 'QTextBlock' is registered using qRegisterMetaType().)
QObject::connect: Cannot queue arguments of type 'QTextCursor'
(Make sure 'QTextCursor' is registered using qRegisterMetaType().)
If I understand your code correctly, you're trying to log from a background thread and are using a direct connection to pass the signal to the GUI thread? That's not going to work, you have to send the signal via the default connection so Qt can figure out that it's a cross-thread signal and pass it across threads accordingly (ie, via the message loop on the foreground thread).
In Qt, any GUI interaction has to happen in the Main/foreground thread otherwise bad things happen as you discovered. You can certainly send a signal from a background thread to trigger a GUI update - I do this all the time - but you need to ensure that you're using the correct connection for it. The direct connection results in a direct function call and is not going to work for you in this case.
In your code, the problem is the call to connect() - you explicitly specify the connection mode for the signal to slot connection when you should just use the default setting. If you set the connection explicitly to Qt::DirectConnection, the underlying code will execute a direct call to the specified slot, which means that you end up calling the slot in the thread context of the signal. You don't want that because the signal is triggered in a background thread.
You can't pass arbitrary types/classes to signals and slots. The list is limited and not all Qt classes are in the list. To add types/classes to the list of those that can be passed to a signal/slot, you must call qRegisterMetaType for that class. I recommend calling it in the constructor of the class you're trying to pass to a signal like this:
MyClass::MyClass() : MyParentClass()
{
static int reg = qRegisterMetaType<MyClass>("MyClass");
}
The static int ensures the registration is only called once and before any instance of MyClass could ever be used.

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.