简体   繁体   中英

Qt signal slot button

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*.

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

testQCounter.exe 的动画快照

Notes:

  1. Concerning the update of the counter value label:
    The signature of QLabel::setText() (the potential slot) is void QLabel::setText(const QString&) .
    The signature of the signal to connect is void 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() and hide() (in gtkmm with sigc++ ). This was a real nightmare…

  2. Counter defines a signal . (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 my CMakeLists.txt for moc (as I usually build without moc).

    The CMakeLists.txt used to build the build script for testQCounter.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

testQCounter2.exe 快照

Notes:

  1. 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:

    1. I'm paranoid.
    2. This won't work for lambdas.
  2. 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 2 nd flavor has an identical signature like Counter::changeValue() and, hence, can be used as slot without an adapter.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM