【问题标题】:Qt signal slot buttonQt 信号槽按钮
【发布时间】:2021-02-12 22:33:36
【问题描述】:

每次单击时,我都需要更改 QLabel 中的文本以更新信息。 当我改变 A 的值时,B 的值也必须改变。 我有两个按钮可以更改两个 QLabel 中的值(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);

如何更改两个 QLabel 中的文本?因为这样它保持 const - 0.... 当我尝试连接时:

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

它写一个错误: 调用不带对象参数的非静态成员函数。

但我向函数传递了一个参数 - QLabel*。

【问题讨论】:

  • 请教一个问题!
  • QObject::connect(&A, &Counter::changeValue, &B, &Counter::Increment); QObject::connect(&A, &Counter::changeValue, &B, &Counter::Decrement ); 这看起来有点可疑:每次更改Counter A 后,计数器B 会递增和递减吗?为什么?
  • 信号clickedAdd()clickedSub() 不应该是class Counter 的一部分。它们应该是使用class Counter 的一部分。
  • 关于您的问题:Counter::clickedAdd(QLabel* obj)QObject::connect() 中是错误的。它需要一个函数或成员函数指针(在您的情况下 - 后者),但您提供了一半的声明。 C++ 编译器不理解这一点并抱怨。顺便提一句。 Counter::clickedAdd() 的签名与信号 QPushButton::clicked 的签名不匹配。为此,您需要一个适配器(包装函数或 lambda),但实际上我相信您的设计已经错误(如上所述)。
  • 当我在 main.cpp 中手动执行时效果很好 设计不佳的事情可能会起作用 - 但仍然设计不佳。 ;-)(这是您在日常业务中每天都能看到的。)

标签: c++ qt signals-slots qpushbutton


【解决方案1】:

假设 OP 想要一个应用程序

  • 两个可以递增和递减的计数器
  • 图形用户界面
    • 显示计数器值
    • 以交互方式递增/递减计数器的按钮。

根据这个需求,我会推导出程序​​的结构:

  • Counter 的类(已由 OP 公开)
  • 图形用户界面。

对于后者,我经常在许多示例代码中看到类,但我相信:对于这样一个最小的 GUI/应用程序,这甚至可以直接在 main() 中完成。

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

输出:

Qt Version: 5.15.1

注意事项:

  1. 关于计数器值标签的更新:
    QLabel::setText()(潜在槽)的签名是void QLabel::setText(const QString&amp;)
    要连接的信号的签名是void Counter::changeValue(int)
    显然,这些签名是不兼容的。

    为了方便,我引入了一个函数

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

    但这并不能解决不兼容问题,因为该函数还有另一个参数QLabel&amp;,它不在发出的信号中。

    这是一个非常常见的情况,非常常见的解决方案是绑定相应的。 QLabel 对信号的引用。 使用lambda 可以最轻松地完成。

    当我第一次看到 C++ 中的 lambda 时,我发现语法不直观,而且有点吓人。 但是,在阅读了文档之后。和教程我已经习惯了,今天,我无法想象没有它。 我必须承认,在我了解 lambdas 之前,我不得不摆弄bind()hide()(在gtkmmsigc++ 中)。这真是一场噩梦……

  2. Counter 定义了一个signal。 (我修正了 OP 的错误语法。)

    为了使这个链接正确,我必须添加一些东西:

    • Q_OBJECT
    • #include "testQCounter.moc"
    • 在我的构建脚本中支持 Qt moc。

    我的构建脚本是一个 Visual Studio 项目,我使用 CMake 脚本准备了它。 我不得不为 moc 扩展我的 CMakeLists.txt(因为我通常在没有 moc 的情况下构建)。

    CMakeLists.txt 用于为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)
    

当然,对于我的制作,我也会使用单独的classes 来处理 GUI 内容。

因此,考虑到示例有两个具有几乎相同 GUI 的计数器,引入一个计数器小部件可能是有意义的 - 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();
}

输出:

Qt Version: 5.15.1

注意事项:

  1. 由于数据模型和 GUI 不再被设计硬连线,我稍微改变了信号槽连接的管理:

    • 连接是按需完成的(CounterEdit::setCounter())。
    • 不再需要时断开连接。

    没有必要像我在示例中那样存储连接。 在 Qt 中,也可以通过提供信号和插槽(如connect())来断开连接。 我不喜欢这个有两个原因:

    1. 我很偏执。
    2. 这不适用于 lambda。
  2. 虽然更新的实际成员函数 (CounterEdit::updateValue()) 是无参数的,但我提供了第二种风格 (CounterEdit::updateValue(int)),它只是一个包装器。

    但是,这 2nd 风格具有与 Counter::changeValue() 相同的签名,因此可以在没有适配器的情况下用作插槽。

【讨论】:

  • 在我的实现中我只需要添加一串你的代码... - 用lambda实现连接!它有效!非常感谢!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-10-28
  • 2016-07-05
  • 2012-09-11
  • 2017-02-23
  • 2011-10-29
  • 1970-01-01
相关资源
最近更新 更多