Search code examples
c++qtsignals-slotsqpushbutton

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


Solution

  • 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
    

    Animated Snapshot of 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 classes 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
    

    Snapshot of 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 2nd flavor has an identical signature like Counter::changeValue() and, hence, can be used as slot without an adapter.