I'm going through the calculator example that was installed with Qt 5.1.1 and there is a private function used to create button widgets (Button inherits QToolButton):
Button *Calculator::createButton(const QString &text, const char *member)
{
Button *button = new Button(text);
connect(button, SIGNAL(clicked()), this, member);
return button;
}
The example calls the above function to create several different buttons e.g.:
createButton(tr("Clear"), SLOT(clear()));
Where void clear() was declared as a private slot. I understand what the code is trying to do but I want to know why does passing SLOT(clear()) as the const char *member work. I can't seem to find much online that would explain using SLOT like that.
As you can see in the documentation of the connect method, the function signature expects the const char* type. These are the corresponding defines from QtCore:
Q_CORE_EXPORT const char *qFlagLocation(const char *method);
...
# define SLOT(a) qFlagLocation("1"#a QLOCATION)
# define SIGNAL(a) qFlagLocation("2"#a QLOCATION)
It is a bit more complex and you can see the details in here, but I simplified it for the sake of the explanation and understanding.
This "old" signal-slot syntax is basically "string" based, and that is also the fundamental flaw with it. This was fixed in Qt 5, however. It is now closer what you seem to imply with your question so that you would rather expect it to be function or method pointers since you eventually pass such an element to the SLOT and SIGNAL moc tokens.
For completeness, the corresponding SIGNAL and SLOT tokens (i.e. Q_SLOTS, Q_SIGNALS, etc) are processed by the meta object compiler, aka. moc, the way that it puts those into the ".moc" files. You can see it yourself if you open those files up. For further details, look into the moc source code which can be found in here.
Related
I'm new to Qt and not a C++ expert so please bear with me with this one.
I'm working on an application that has 12 different QPushButtons, but all of them perform very similar actions: they grab some value from a map and then use it to set the style for that button:
void MainWindow::on_btn01_clicked(){
QString img=images["btn01"];
ui->btn01->setStyleSheet("#btn01{ background-image: url(://" + img + ") }");
}
For each one of the 12 buttons I have to create a slot that only differs in the button being used. So it looks a bit weird to create 12 functions that are almost identical.
Is there a better way to do this?
Generally, there are several approaches I've seen used:
The preferred method: lambda expressions.
If you're using modern C++ (C++11 or newer), you can use a lambda function to get the exact effect you described.
I'd expect the resulting code to look something like this:
foreach( QPushButton * button : buttons ) {
connect( button, &QPushButton::clicked, [button, &images]() {
button->setStyleSheet( QString( "{ background-image: url(://%1) }" )
.arg( images[button->objectName()] ) );
});
}
See "Qt Slots and C++11 lambda" for more guidance on how to write lambda functions with Qt.
A dirtier way: QObject::sender().
Qt lets you use QObject::sender() within a slot to get a pointer to the object that invoked it. This has been a widely used approach for many years, but there are limitations, as described in the Qt documentation:
Warning: As mentioned above, the return value of this function is not valid when the slot is called via a Qt::DirectConnection from a thread different from this object's thread. Do not use this function in this type of scenario.
See "How to get sender widget with a signal/slot mechanism?" for more information.
Obsolete code: QSignalMapper.
There's a QSignalMapper class that will let you associate a signal with a bit of data, like a string, to then connect to a slot that uses this parameter. This used to exist as a convenience class in Qt but is being phased out because the lambda methodology makes it pointless.
If you need to update code that uses QSignalMapper to remove it, this tutorial is a reasonable starting point.
You can try one more simple method, that is set the QPushButton's object name respectively, and check the Object name in your slot and use that string. This saves you a lot of code.
Ex:
QPushButton 1 object name is set as button->setObjectName("btn01"); and respectively you can set the other names of the buttons and in your slot you could do some thing like this
void MainWindow::on_button_clicked(){
QPushButton* btn=qobject_cast<QPushButton*>(sender());
QString name=btn->objectName();
QString img=images[name]; btn->setStyleSheet("#" + name + "{ background-image: url(://" + img + ");
}
and then connect all your QPushButtons to this slot
I'm designed a QTableWidget with QPushButton, I would like to connect these buttons with a slot to hide some rows.
I'm using a lambda expression to pass a number of a row. But the compiler doesn't recognized this expression :
connect(this->ui->tableWidget->cellWidget(i,0),&QPushButton::clicked,[this,i]{hideRows(i);});
I have this error:
error: no matching function for call to 'SoftwareUdpater::MainWidget::connect(QWidget*, void (QAbstractButton::*)(bool), SoftwareUdpater::MainWidget::displayTable()::<lambda(int)>)'
The function hideRows(int) is declared as a function. And, as a slot, it doesn't work,
CONFIG += c++11 is added in pro file,
My class MainWidget inherits from QWidget,
Q_OBJECT is added in the header.
So I don't udnerstand why connect() is not recognized by Qt 5.9.1 MinGw 32bit.
Edit: [this,i]() instead of [this](const int i) for the lambda expression
Your connection is wrong. You can't connect a function that doesn't take parameters (clicked()) with a function that takes parameters (your lambda). To verify that this is the case, just do this:
connect(this->ui->tableWidget->cellWidget(i,0),&QPushButton::clicked,[this](){});
And see that it will compile. You have to make your design in such a way that signals and slots are compatible.
Also avoid using lambdas in signals and slots. Read the caveats here.
I was reading your comments on the accepted answer and noticed the root of the problem: This error is being thrown because the effective type of the object — as supplied to QObject::connect; i.e QWidget in your case — does not define the referenced signal QPushButton::clicked.
What likely happened is that the QPushButton pointer was cast into a QWidget and then that pointer was given to connect instead of the original which defines the signal.
Cast the pointer back to a QPushButton * and the error should go away.
digitButtons[i] = createButton (QString::number(i), SLOT(digitClicked()));
and
Button *Calculator::createButton(const QString &text, const char *member)
{
Button *button = new Button(text);
connect(button, SIGNAL(clicked()), this, member);
return button;
}
That code is from Calculator example of Qt docs.
In this doc http://doc.qt.io/qt-4.8/signalsandslots.html, I couldn't find the type of SLOT.
Where is it mentioned that SLOt is QString?
Yes as stated by Starl1ght SLOT and SIGNAL are macro of the Meta-Object Compiler, that's why they have no data type.
In the example you have mentioned, it's used const char * because "SLOT is passed by name" to connection function. Have a look here http://doc.qt.io/qt-4.8/qobject.html#connect
Hope this briefly clarify a little bit what are SIGNALS and SLOTS.
SLOT and SIGNAL are both macro for qt MOC.
They are defined simply as:
#define SLOT(a) "1"#a
#define SIGNAL(a) "2"#a
Before compilation stage, MOC will find such lines and generate valid .moc file with c++ code, include them in your project and thus, signal\slots shall work.
private:
Button *createButton(const QString &text, const char *member);
void abortOperation();
bool calculate(double rightOperand, const QString &pendingOperator);
Button *pointButton = createButton(tr("."), SLOT(pointClicked()));
In qt's calculator example: http://qt-project.org/doc/qt-4.8/widgets-calculator.html
The createButton member function takes two constant string. Then why we are passing slots to them as a second argument?
Simplest summary: The create button function allocates a new button, sets the text, and then connects that button's clicked signal to the slot represented with the string you sent in.
Button *Calculator::createButton(const QString &text, const char *member)
{
Button *button = new Button(text);
//NOTE right here it uses the string you passed in - BEGIN
connect(button, SIGNAL(clicked()), this, member);
//NOTE right here it uses the string you passed in - END
return button;
}
A little bit more detail as to why the signal and slot macros are compatable with strings like this (per this previous stack overflow post):
As Neil said, the SLOT and SIGNAL macros are defined as
> #define SLOT(a) "1"#a
> #define SIGNAL(a) "2"#a
The #a (with # a stringizing operator) will simply
turn whatever is put within the parentheses into a string
literal, to create names from the signatures provided to the macros.
The "1" and "2" are merely there to distinguish between slots and
signals.
This earlier post should provide you some more insight.
If you wonder about the "why?" of all this macro stuff and
preprocessing, I would suggest you read up on the
"Meta-Object-Compiler" or MOC. And just for fun you could have a look
at what MOC does to the code you provide it with. Look through its
output and see what it contains. That should be quite informative.
In short, this preprocessing through MOC allows Qt to implement some
features (like the signals and slots) which C++ does not provide as
standard. (Although there are arguably some implementations of this
concept, not related to Qt, which don't require a Meta Object
Compiler)
Hope that helps.
Please note the post I linked has other links of value, that didn't come through with the copy and paste.
I have two instances of QObject subclasses and two QMetaMethod instances of signal in one of objects and slot in another object. I want to connect this signal and slot with each other.
I've looked through the qobject.h file and find that SIGNAL() and SLOT() macro are just add "1" or "2" character to the beginning of method signature so it looks like it should be possible to add the same character to the beginning of string returned by QMetaMethod::signature() but this approach depends on some undocumented internals of toolkit and may be broken at any time by a new version of Qt.
Does anybody know reliable way to connect signals and slots through their QMetaMethod reflection representation?
Edited:
I've created suggestion in Qt issue tracker:
https://bugreports.qt.io/browse/QTBUG-10637
If anybody also interested in this feature you can vote for this ticket there.
This has been fixed as of Qt 4.8.0:
https://bugreports.qt.io/browse/QTBUG-10637
Suppose we have a QObject* m_subject, and wish to connect the change-notification signal of a property to a propertyChanged() slot:
const QMetaObject* meta = m_subject->metaObject();
QMetaProperty prop = meta->property(meta->indexOfProperty("myProperty"));
if (prop.hasNotifySignal()) {
QMetaMethod signal = prop.notifySignal();
QMetaMethod updateSlot = metaObject()->method(
metaObject()->indexOfSlot("propertyChanged()"));
connect(m_subject, signal, this, updateSlot);
}
I successfully used this to make a QWidget subclass which finds all the properties of any QObject and creates a QLineEdit for each of them, with a connection to keep the QLineEdit updated whenever the corresponding property changes. (Because I didn't find a way to pass a propertyID value to propertyChanged() though, it was necessary to make a subclass of QLineEdit and implement propertyChanged() there. QSignalMapper didn't help, because all the properties are in the same object.)
Thanks to MBack, I now use metamethods to connect my view to my model's properties dynamically for MVVM or MVC pattern.
In order to respect DRY, a boilerplate is required with something like this :
void MyClass::connectSignalToSlot(QObject* sender, std::string signalName, QObject* receiver, std::string slotName)
{
int sigIdx = sender->metaObject()->indexOfSignal(signalName.c_str());
auto signal = sender->metaObject()->method(sigIdx);
int slotIdx = receiver->metaObject()->indexOfSlot(slotName.c_str());
auto slot = receiver->metaObject()->method(slotIdx);
connect(sender,signal,receiver,slot);
}
void MyClass::connectPropertyChangedToSlot(QObject* sender, std::string propName, QObject* receiver, std::string slotName)
{
int sigIdx = sender->metaObject()->indexOfProperty(propName.c_str());
auto signal = sender->metaObject()->property(sigIdx ).notifySignal();
int slotIdx = receiver->metaObject()->indexOfSlot(slotName.c_str());
auto slot = receiver->metaObject()->method(slotIdx);
return connect(sender, signal, receiver, slot);
}
It looks like there is no way to make it work without relying on internal implementation. If I were you, I'd submit feature request to Qt bug tracker, write a code that mimics current behavior SIGNAL/SLOT macros and add unit test that will fail when SIGNAL/SLOT behavior changes.
There might be a simpler solution to the problem you're trying to solve: describe what exactly are you trying to do without any implementation details.
If signature method is public in QMetaMethod then the result shouldn't be broken by trolls and it's safe to use it (documentation says nothing about "dangers" when using QMetaMethod::signature method). I think you can safely use it. Just to be sure, what version of Qt you are using right now ?