问题
I need to change my text in QLabel every time when I clicked in order to my info updates. When I change the value of A, the value of B must change, too. I have two button that can change values in two QLabel (value of A, value of B).
main.cpp:
Counter A, B;
QObject::connect(&A, &Counter::changeValue, &B, &Counter::setValue);
QObject::connect(&A, &Counter::changeValue, &B, &Counter::Increment);
QObject::connect(&A, &Counter::changeValue, &B, &Counter::Decrement );
QObject::connect(Add, &QPushButton::clicked, &A, &Counter::clickedAdd(QLabel* obj));
QObject::connect(Sub, &QPushButton::clicked, &B, &Counter::clickedSub(QLabel* obj));
class Counter: public QObject{
private:
int count;
public slots:
int Increment () {
count++;
emit changeValue(count);
}
int Decrement () {
count--;
emit changeValue(count);
}
void clickedAdd(QLabel* obj){
int new_count = Increment();
obj_label->setText(QString::number(new_count));_
}
void clickedSub(QLabel* obj){
int new_count = Deccrement();
obj_label->setText(QString::number(new_count));_
}
void setValue(int new_count){
m_count = new_count;
emit changeValue(new_count);
}
public signals:
void changeValue(int);
How can I change my text in two QLabel's? Because in this way it stays const - 0.... When I try to connect:
QObject::connect(Add, &QPushButton::clicked, &A, &Counter::clickedAdd(QLabel* obj));
It writes an error: Call to non-static member function without an object argument.
But I pass the function an argument - QLabel*.
回答1:
Assuming OP wants an application with
- two counters which can be incremented and decremented
- a GUI
- displaying the counter values
- buttons to increment/decrement the counters interactively.
From this requirement, I would derive the structure of the program:
- a class for the
Counter
(as already exposed by OP) - a GUI.
For the latter, I often saw classes as well in numerous sample codes but I believe: for such a minimal GUI / application, this could even all be done in main()
directly.
testQCounter.cc
:
#include <iostream>
#include <string>
// Qt header:
#include <QtWidgets>
// OPs Counter Class
class Counter : public QObject {
Q_OBJECT
private:
int count = 0;
public slots:
int Increment() {
count++;
emit changeValue(count);
return count;
}
int Decrement() {
count--;
emit changeValue(count);
return count;
}
int getValue() const { return count; }
void setValue(int new_count) {
count = new_count;
emit changeValue(new_count);
}
signals:
void changeValue(int);
};
#include "testQCounter.moc"
void setLabelValue(QLabel& qLbl, int value)
{
qLbl.setText(QString::number(value));
}
// main application
int main(int argc, char **argv)
{
qDebug() << "Qt Version:" << QT_VERSION_STR;
QApplication app(argc, argv);
// setup data
Counter a, b;
// setup GUI
QWidget qWinMain;
qWinMain.setWindowTitle("Counter Sample");
QGridLayout qGrid;
QLabel qLblATitle("Counter A");
qGrid.addWidget(&qLblATitle, 0, 0);
QPushButton qBtnIncA("+");
qGrid.addWidget(&qBtnIncA, 1, 0);
QLabel qLblA;
qLblA.setAlignment(Qt::AlignRight);
qLblA.setFrameStyle(QLabel::Box);
qGrid.addWidget(&qLblA, 2, 0);
QPushButton qBtnDecA("-");
qGrid.addWidget(&qBtnDecA, 3, 0);
QLabel qLblBTitle("Counter B");
qGrid.addWidget(&qLblBTitle, 0, 1);
QPushButton qBtnIncB("+");
qGrid.addWidget(&qBtnIncB, 1, 1);
QLabel qLblB("");
qLblB.setAlignment(Qt::AlignRight);
qLblB.setFrameStyle(QLabel::Box);
qGrid.addWidget(&qLblB, 2, 1);
QPushButton qBtnDecB("-");
qGrid.addWidget(&qBtnDecB, 3, 1);
qWinMain.setLayout(&qGrid);
qWinMain.show();
setLabelValue(qLblA, a.getValue());
setLabelValue(qLblB, b.getValue());
// install signal handlers
// connect clicked signal of buttons to counter a
QObject::connect(&qBtnDecA, &QPushButton::clicked, &a, &Counter::Decrement);
QObject::connect(&qBtnIncA, &QPushButton::clicked, &a, &Counter::Increment);
// connect changeValue signal of counter a to a function
QObject::connect(&a, &Counter::changeValue,
[&](int value) { setLabelValue(qLblA, value); });
// connect clicked signal of buttons to counter b
QObject::connect(&qBtnDecB, &QPushButton::clicked, &b, &Counter::Decrement);
QObject::connect(&qBtnIncB, &QPushButton::clicked, &b, &Counter::Increment);
// connect changeValue signal of counter b to b function
QObject::connect(&b, &Counter::changeValue,
[&](int value) { setLabelValue(qLblB, value); });
// runtime loop
return app.exec();
}
Output:
Qt Version: 5.15.1
Notes:
Concerning the update of the counter value label:
The signature ofQLabel::setText()
(the potential slot) isvoid QLabel::setText(const QString&)
.
The signature of the signal to connect isvoid Counter::changeValue(int)
.
Obviously, these signatures are not compatible.For convenience, I introduced a function
void setLabelValue(QLabel& qLbl, int value) { qLbl.setText(QString::number(value)); }
but this doesn't fix the incompatibility because the function still has another parameter
QLabel&
which is not in the emitted signal.This is a very usual case and the very usual solution is to bind the resp.
QLabel
reference to the signal. It can be done most easily using a lambda.When I first saw lambdas in C++, I found the syntax non-intuitive and somehow scaring. However, after having read the doc. and tutorials I got used to it, and today, I couldn't imagine to live without. I must admit before I learnt about lambdas I had to fiddle with
bind()
andhide()
(ingtkmm
withsigc++
). This was a real nightmare…Counter
defines asignal
. (I fixed the wrong syntax of OP.)To make this linking properly, I had to add some things:
Q_OBJECT
#include "testQCounter.moc"
- support for the Qt moc in my build script.
My build script is a Visual Studio project which I prepared with a
CMake
script. I had to extend myCMakeLists.txt
for moc (as I usually build without moc).The
CMakeLists.txt
used to build the build script fortestQCounter.cc
:project(QCounter) cmake_minimum_required(VERSION 3.10.0) set_property(GLOBAL PROPERTY USE_FOLDERS ON) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) find_package(Qt5 COMPONENTS Widgets REQUIRED) set(CMAKE_AUTOMOC ON) set(CMAKE_INCLUDE_CURRENT_DIR ON) include_directories("${CMAKE_SOURCE_DIR}") add_executable(testQCounter testQCounter.cc) target_link_libraries(testQCounter Qt5::Widgets)
For my production, I would use separate class
es for the GUI stuff as well, of course.
So, considering that the sample has two counters with nearly identical GUIs, it may make sense to introduce a counter widget – CounterEdit
.
testQCounter2.cc
:
#include <iostream>
#include <string>
// Qt header:
#include <QtWidgets>
// OPs Counter Class
class Counter : public QObject {
Q_OBJECT
private:
int count = 0;
public slots:
int Increment() {
count++;
emit changeValue(count);
return count;
}
int Decrement() {
count--;
emit changeValue(count);
return count;
}
int value() const { return count; }
void setValue(int new_count) {
count = new_count;
emit changeValue(new_count);
}
signals:
void changeValue(int);
};
#include "testQCounter2.moc"
class CounterEdit : public QWidget {
private:
Counter* pCounter = nullptr;
QVBoxLayout qVBox;
QLabel qLblTitle;
QPushButton qBtnInc;
QLabel qLblValue;
QPushButton qBtnDec;
QMetaObject::Connection connectionInc;
QMetaObject::Connection connectionDec;
QMetaObject::Connection connectionValue;
public:
CounterEdit(const QString& title, QWidget* pQParent = nullptr) :
QWidget(pQParent),
qLblTitle(title),
qBtnInc("+"),
qLblValue(""),
qBtnDec("-")
{
qLblTitle.setAlignment(Qt::AlignCenter);
qVBox.addWidget(&qLblTitle);
qVBox.addWidget(&qBtnInc);
qLblValue.setAlignment(Qt::AlignRight);
qLblValue.setFrameStyle(QLabel::Box);
qVBox.addWidget(&qLblValue);
qVBox.addWidget(&qBtnDec);
setLayout(&qVBox);
}
virtual ~CounterEdit()
{
QObject::disconnect(connectionInc);
QObject::disconnect(connectionDec);
QObject::disconnect(connectionValue);
}
CounterEdit(const CounterEdit&) = delete;
CounterEdit& operator=(const CounterEdit&) = delete;
Counter* counter() { return pCounter; }
const Counter* counter() const { return pCounter; }
void updateValue();
void updatevalue(int) { updateValue(); }
void setCounter(Counter* pCounter);
};
void CounterEdit::updateValue()
{
if (pCounter) {
qLblValue.setText(QString::number(pCounter->value()));
} else {
qLblValue.setText(QString());
}
}
void CounterEdit::setCounter(Counter* pCounter)
{
QObject::disconnect(connectionInc);
QObject::disconnect(connectionDec);
QObject::disconnect(connectionValue);
this->pCounter = pCounter;
if (pCounter) {
qLblValue.setText(QString::number(pCounter->value()));
connectionInc
= QObject::connect(&qBtnInc, &QPushButton::clicked, pCounter, &Counter::Increment);
connectionDec
= QObject::connect(&qBtnDec, &QPushButton::clicked, pCounter, &Counter::Decrement);
connectionValue
= QObject::connect(pCounter, &Counter::changeValue, this, &CounterEdit::updateValue);
}
}
// main application
int main(int argc, char **argv)
{
qDebug() << "Qt Version:" << QT_VERSION_STR;
QApplication app(argc, argv);
// setup data
Counter a, b;
// setup GUI
QWidget qWinMain;
qWinMain.setWindowTitle("Counter Sample");
QHBoxLayout qHBox;
CounterEdit editA("Counter A:");
qHBox.addWidget(&editA);
CounterEdit editB("Counter B:");
qHBox.addWidget(&editB);
qWinMain.setLayout(&qHBox);
qWinMain.show();
editA.setCounter(&a);
editB.setCounter(&b);
// runtime loop
return app.exec();
}
Output:
Qt Version: 5.15.1
Notes:
As data model and GUI are not anymore hardwired by design, I changed the management of the signal-slot connections a bit:
- The connections are done on demand (in
CounterEdit::setCounter()
). - Connections are disconnected when not anymore needed.
It's not strictly necessary to store the connections like I did in the sample. In Qt, a connection may be disconnected as well by providing signal and slot like in
connect()
. I don't like this for two reasons:- I'm paranoid.
- This won't work for lambdas.
- The connections are done on demand (in
While the actual member function for update (
CounterEdit::updateValue()
) is parameter-less, I provided a second flavor (CounterEdit::updateValue(int)
) which is just a wrapper.However, this 2nd flavor has an identical signature like
Counter::changeValue()
and, hence, can be used as slot without an adapter.
来源:https://stackoverflow.com/questions/64608786/qt-signal-slot-button