Nút khe tín hiệu Qt

Oct 30 2020

Tôi cần thay đổi văn bản của mình trong QLabel mỗi khi tôi nhấp vào để cập nhật thông tin của mình. Khi tôi thay đổi giá trị của A thì giá trị của B cũng phải thay đổi. Tôi có hai nút có thể thay đổi giá trị trong hai QLabel (giá trị của A, giá trị của 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);

Làm cách nào để thay đổi văn bản của tôi trong hai QLabel? Bởi vì theo cách này, nó vẫn là const - 0 .... Khi tôi cố gắng kết nối:

QObject::connect(Add, &QPushButton::clicked, &A, &Counter::clickedAdd(QLabel* obj));

Nó viết một lỗi: Gọi đến hàm thành viên không tĩnh mà không có đối số đối tượng.

Nhưng tôi truyền cho hàm một đối số - QLabel *.

Trả lời

1 Scheff Oct 30 2020 at 15:35

Giả sử OP muốn một ứng dụng với

  • hai bộ đếm có thể được tăng và giảm
  • một GUI
    • hiển thị các giá trị bộ đếm
    • để tăng / giảm bộ đếm một cách tương tác.

Từ yêu cầu này, tôi sẽ rút ra cấu trúc của chương trình:

  • một lớp cho Counter(như đã được OP giới thiệu)
  • một GUI.

Đối với phần sau, tôi thường thấy các lớp cũng như trong nhiều mã mẫu nhưng tôi tin rằng: đối với một ứng dụng / GUI tối thiểu như vậy, điều này thậm chí có thể được thực hiện main()trực tiếp.

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

Đầu ra:

Qt Version: 5.15.1

Ghi chú:

  1. Liên quan đến việc cập nhật nhãn giá trị bộ đếm:
    Chữ ký của QLabel::setText()(vị trí tiềm năng) là void QLabel::setText(const QString&).
    Chữ ký của tín hiệu để kết nối là void Counter::changeValue(int).
    Rõ ràng, những chữ ký này không tương thích.

    Để thuận tiện, tôi đã giới thiệu một chức năng

    void setLabelValue(QLabel& qLbl, int value)
    {
      qLbl.setText(QString::number(value));
    }
    

    nhưng điều này không khắc phục được sự không tương thích vì hàm vẫn có một tham số khác QLabel&không có trong tín hiệu phát ra.

    Đây là một trường hợp rất bình thường và giải pháp rất thông thường là ràng buộc các hồi. QLabeltham chiếu đến tín hiệu. Nó có thể được thực hiện dễ dàng nhất bằng cách sử dụng lambda .

    Khi tôi lần đầu tiên nhìn thấy lambdas trong C ++, tôi thấy cú pháp không trực quan và bằng cách nào đó đáng sợ. Tuy nhiên, sau khi đọc tài liệu. và các bài hướng dẫn mà tôi đã quen với nó, và ngày nay, tôi không thể tưởng tượng sẽ sống thiếu nó. Tôi phải thừa nhận rằng trước khi tìm hiểu về lambdas, tôi đã phải mày mò bind()và hide()(tham gia gtkmmvới sigc++). Đây là một cơn ác mộng thực sự…

  2. Counterxác định a signal. (Tôi đã sửa cú pháp sai của OP.)

    Để thực hiện liên kết này đúng cách, tôi phải thêm một số thứ:

    • Q_OBJECT
    • #include "testQCounter.moc"
    • hỗ trợ cho Qt moc trong kịch bản xây dựng của tôi.

    Tập lệnh xây dựng của tôi là một dự án Visual Studio mà tôi đã chuẩn bị với một CMaketập lệnh. Tôi đã phải mở rộng của tôi CMakeLists.txtcho moc (như tôi thường xây dựng mà không có moc).

    Được CMakeLists.txtsử dụng để xây dựng tập lệnh xây dựng cho 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)
    

Tất nhiên, đối với quá trình sản xuất của tôi, tôi cũng sẽ sử dụng các classes riêng biệt cho các công cụ GUI.

Vì vậy, xem xét rằng mẫu có hai bộ đếm với GUI gần giống nhau, có thể hợp lý khi giới thiệu một tiện ích bộ đếm - 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();
}

Đầu ra:

Qt Version: 5.15.1

Ghi chú:

  1. Vì mô hình dữ liệu và GUI không còn được thiết kế cứng cáp nữa, tôi đã thay đổi cách quản lý các kết nối khe cắm tín hiệu một chút:

    • Các kết nối được thực hiện theo yêu cầu (trong CounterEdit::setCounter()).
    • Kết nối bị ngắt khi không cần thiết nữa.

    Không nhất thiết phải lưu trữ các kết nối như tôi đã làm trong mẫu. Trong Qt, một kết nối cũng có thể bị ngắt bằng cách cung cấp tín hiệu và khe cắm như trong connect(). Tôi không thích điều này vì hai lý do:

    1. Tôi là người hoang tưởng.
    2. Điều này sẽ không hoạt động cho lambdas.
  2. Trong khi hàm thành viên thực tế cho update ( CounterEdit::updateValue()) là ít tham số, tôi đã cung cấp một hàm thứ hai ( CounterEdit::updateValue(int)) chỉ là một trình bao bọc.

    Tuy nhiên, hương vị thứ 2 này có một chữ ký giống hệt nhau như Counter::changeValue()và do đó, có thể được sử dụng làm khe cắm mà không cần bộ điều hợp.