Qt。链接到最新对象插槽的信号
Qt. signal linked to slot of latest object
我正在运行时创建多个 QWidget_WindowContact 类型的对象。当我单击增量或减量按钮时,最后生成的对象中的值得到更新,而不是同一对象中的值。
我正在努力正确 link 信号和槽,以便在运行时生成多个相同类型的对象时,按钮发出的信号对应正确对象中的信号。
QWidget_WindowContact.h:
#ifndef QWIDGET_WINDOWCONTACT_H
#define QWIDGET_WINDOWCONTACT_H
#include "qwidget.h"
class QWidget_WindowContact : public QWidget
{
Q_OBJECT
public:
explicit QWidget_WindowContact(QWidget* parent = nullptr);
private slots:
void on_btnInc_clicked();
void on_btnDec_clicked();
void on_btnDel_clicked();
private:
QPoint offset;
protected:
};
#endif // QWIDGETTEMP_H
QWidget_WindowContact.h:
#include <QtWidgets>
#include "QWidget_WindowContact.h"
QSpinBox* counter;
QWidget_WindowContact::QWidget_WindowContact(QWidget* parent) : QWidget(parent)
{
this->setObjectName("");
setMinimumSize(100, 100);
this->setStyleSheet("border: 1px solid gray");
QVBoxLayout* layout = new QVBoxLayout(this);
QLabel* name = new QLabel("WINDOWCONTACT", this);
QPushButton* btnInc = new QPushButton("Increment", this);
btnInc->setObjectName("btnInc");
QPushButton* btnDec = new QPushButton("Decrement", this);
btnDec->setObjectName("btnDec");
QPushButton* btnDel = new QPushButton("Delete Widget", this);
btnDel->setObjectName("btnDel");
counter = new QSpinBox(this);
counter->setObjectName("counter");
connect(this->findChild<QPushButton*>("btnInc"), SIGNAL(clicked()), this, SLOT(on_btnInc_clicked()));
connect(this->findChild<QPushButton*>("btnDec"), SIGNAL(clicked()), this, SLOT(on_btnDec_clicked()));
connect(this->findChild<QPushButton*>("btnDel"), SIGNAL(clicked()), this, SLOT(on_btnDel_clicked()));
layout->addWidget(name);
layout->addWidget(counter);
layout->addWidget(btnInc);
layout->addWidget(btnDec);
layout->addWidget(btnDel);
}
void QWidget_WindowContact::on_btnInc_clicked()
{
counter->setValue(counter->value() + 1);
}
void QWidget_WindowContact::on_btnDec_clicked()
{
counter->setValue(counter->value() - 1);
}
void QWidget_WindowContact::on_btnDel_clicked()
{
close();
}
你有
QSpinBox* counter;
作为全局变量,每次创建新的 QWidget_WindowContact
时都会更改其值。这段代码:
void QWidget_WindowContact::on_btnInc_clicked()
{
counter->setValue(counter->value() + 1);
}
正在使用这个全局变量,它始终是指向最近创建的 QSpinBox
的指针。删除全局变量并将此 QSpinBox*
ptr 保留为您的 class 的私有成员,或者使用 findChild
QT 功能:
void QWidget_WindowContact::on_btnInc_clicked()
{
QSpinBox* counter = this->findChild<QSpinBox*>("counter");
counter->setValue(counter->value() + 1);
}
作为旁注,findChild
在您的 CTor 中实际上并不需要,因为您仍然有指向范围内相应对象的指针。
一般来说,除非您需要调试工具或内省,否则不需要设置小部件名称。使用模板 class 可以在构造时轻松添加命名,也可以插入到布局中。这减少了冗长。请注意,WQ
是一个库 class,您只需要编写一次即可广泛使用,因此不必担心它有多“复杂”——尽管它当然应该使用实际的标签分发头库到
#include <QtWidgets>
static inline constexpr auto name_ = [] {};
static inline constexpr auto layout_ = [] {};
static inline constexpr auto text_ = [] {};
template<class Base>
class WQ : public Base
{
public:
WQ() = default;
template<class... Args>
WQ(decltype(layout_), QLayout *layout, Args &&...args) : WQ(std::forward<Args>(args)...)
{
layout->addWidget(this);
}
template<class... Args>
WQ(decltype(name_), QStringView name, Args &&...args) : WQ(std::forward<Args>(args)...)
{
this->setObjectName(name.toString());
}
template<class... Args>
WQ(decltype(text_), QStringView text, Args &&...args) : WQ(std::forward<Args>(args)...)
{
this->setText(text.toString());
}
};
小部件可以非常紧凑,并以声明方式构建 - 不要从作为 Qt 示例的代码中获得灵感,它不必要地冗长。我们可以在 class:
的正文中设置大多数“短”小部件属性
class QWidget_WindowContact : public QWidget
{
Q_OBJECT
using Self = QWidget_WindowContact;
public:
explicit QWidget_WindowContact(QWidget *parent = nullptr);
private:
void inc_clicked();
void dec_clicked();
QPoint offset;
QVBoxLayout layout{this};
WQ<QLabel> name{::layout_, &layout, ::text_, tr("WINDOWCONTACT")};
WQ<QPushButton> btnInc{::layout_, &layout, ::text_, tr("Increment")};
WQ<QPushButton> btnDec{::layout_, &layout, ::text_, tr("Decrement")};
WQ<QPushButton> btnDel{::layout_, &layout, ::text_, tr("Delete Widget")};
WQ<QSpinBox> counter{::layout_, &layout, ::name_, L"counter"};
};
那么,构造函数也很简单:
QWidget_WindowContact::QWidget_WindowContact(QWidget *parent) : QWidget(parent)
{
setMinimumSize(100, 100);
setStyleSheet("border: 1px solid gray");
connect(&btnInc, &QPushButton::clicked, this, &Self::inc_clicked);
connect(&btnDec, &QPushButton::clicked, this, &Self::dec_clicked);
connect(&btnDel, &QPushButton::clicked, this, &Self::close);
}
插槽保持原样,除非您需要进行自省,否则无需实际制作插槽。不要将方法包装在“什么都不做”而只是转发调用的槽中——这是不必要的。还要注意现代 (Qt 5) 风格的信号槽连接的使用。另请注意,当前的 Qt 版本是 Qt 6.1 - 因此现代信号槽语法很难“现代”。
void QWidget_WindowContact::inc_clicked()
{
counter.setValue(counter.value() + 1);
}
void QWidget_WindowContact::dec_clicked()
{
counter.setValue(counter.value() - 1);
}
该示例在外部 window:
内设置了一个 3x3 的小部件网格
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QWidget w;
QGridLayout layout{&w};
constexpr int rows = 3, cols = 3;
QWidget_WindowContact wc[rows * cols];
for (int i = 0; i < rows; ++i)
for (int j = 0; j < cols; ++j)
layout.addWidget(&wc[i * cols + j], i, j);
w.show();
return a.exec();
}
#include "main.moc"
可编译示例到此结束。它看起来像这样:
我正在运行时创建多个 QWidget_WindowContact 类型的对象。当我单击增量或减量按钮时,最后生成的对象中的值得到更新,而不是同一对象中的值。
我正在努力正确 link 信号和槽,以便在运行时生成多个相同类型的对象时,按钮发出的信号对应正确对象中的信号。
QWidget_WindowContact.h:
#ifndef QWIDGET_WINDOWCONTACT_H
#define QWIDGET_WINDOWCONTACT_H
#include "qwidget.h"
class QWidget_WindowContact : public QWidget
{
Q_OBJECT
public:
explicit QWidget_WindowContact(QWidget* parent = nullptr);
private slots:
void on_btnInc_clicked();
void on_btnDec_clicked();
void on_btnDel_clicked();
private:
QPoint offset;
protected:
};
#endif // QWIDGETTEMP_H
QWidget_WindowContact.h:
#include <QtWidgets>
#include "QWidget_WindowContact.h"
QSpinBox* counter;
QWidget_WindowContact::QWidget_WindowContact(QWidget* parent) : QWidget(parent)
{
this->setObjectName("");
setMinimumSize(100, 100);
this->setStyleSheet("border: 1px solid gray");
QVBoxLayout* layout = new QVBoxLayout(this);
QLabel* name = new QLabel("WINDOWCONTACT", this);
QPushButton* btnInc = new QPushButton("Increment", this);
btnInc->setObjectName("btnInc");
QPushButton* btnDec = new QPushButton("Decrement", this);
btnDec->setObjectName("btnDec");
QPushButton* btnDel = new QPushButton("Delete Widget", this);
btnDel->setObjectName("btnDel");
counter = new QSpinBox(this);
counter->setObjectName("counter");
connect(this->findChild<QPushButton*>("btnInc"), SIGNAL(clicked()), this, SLOT(on_btnInc_clicked()));
connect(this->findChild<QPushButton*>("btnDec"), SIGNAL(clicked()), this, SLOT(on_btnDec_clicked()));
connect(this->findChild<QPushButton*>("btnDel"), SIGNAL(clicked()), this, SLOT(on_btnDel_clicked()));
layout->addWidget(name);
layout->addWidget(counter);
layout->addWidget(btnInc);
layout->addWidget(btnDec);
layout->addWidget(btnDel);
}
void QWidget_WindowContact::on_btnInc_clicked()
{
counter->setValue(counter->value() + 1);
}
void QWidget_WindowContact::on_btnDec_clicked()
{
counter->setValue(counter->value() - 1);
}
void QWidget_WindowContact::on_btnDel_clicked()
{
close();
}
你有
QSpinBox* counter;
作为全局变量,每次创建新的 QWidget_WindowContact
时都会更改其值。这段代码:
void QWidget_WindowContact::on_btnInc_clicked()
{
counter->setValue(counter->value() + 1);
}
正在使用这个全局变量,它始终是指向最近创建的 QSpinBox
的指针。删除全局变量并将此 QSpinBox*
ptr 保留为您的 class 的私有成员,或者使用 findChild
QT 功能:
void QWidget_WindowContact::on_btnInc_clicked()
{
QSpinBox* counter = this->findChild<QSpinBox*>("counter");
counter->setValue(counter->value() + 1);
}
作为旁注,findChild
在您的 CTor 中实际上并不需要,因为您仍然有指向范围内相应对象的指针。
一般来说,除非您需要调试工具或内省,否则不需要设置小部件名称。使用模板 class 可以在构造时轻松添加命名,也可以插入到布局中。这减少了冗长。请注意,WQ
是一个库 class,您只需要编写一次即可广泛使用,因此不必担心它有多“复杂”——尽管它当然应该使用实际的标签分发头库到
#include <QtWidgets>
static inline constexpr auto name_ = [] {};
static inline constexpr auto layout_ = [] {};
static inline constexpr auto text_ = [] {};
template<class Base>
class WQ : public Base
{
public:
WQ() = default;
template<class... Args>
WQ(decltype(layout_), QLayout *layout, Args &&...args) : WQ(std::forward<Args>(args)...)
{
layout->addWidget(this);
}
template<class... Args>
WQ(decltype(name_), QStringView name, Args &&...args) : WQ(std::forward<Args>(args)...)
{
this->setObjectName(name.toString());
}
template<class... Args>
WQ(decltype(text_), QStringView text, Args &&...args) : WQ(std::forward<Args>(args)...)
{
this->setText(text.toString());
}
};
小部件可以非常紧凑,并以声明方式构建 - 不要从作为 Qt 示例的代码中获得灵感,它不必要地冗长。我们可以在 class:
的正文中设置大多数“短”小部件属性class QWidget_WindowContact : public QWidget
{
Q_OBJECT
using Self = QWidget_WindowContact;
public:
explicit QWidget_WindowContact(QWidget *parent = nullptr);
private:
void inc_clicked();
void dec_clicked();
QPoint offset;
QVBoxLayout layout{this};
WQ<QLabel> name{::layout_, &layout, ::text_, tr("WINDOWCONTACT")};
WQ<QPushButton> btnInc{::layout_, &layout, ::text_, tr("Increment")};
WQ<QPushButton> btnDec{::layout_, &layout, ::text_, tr("Decrement")};
WQ<QPushButton> btnDel{::layout_, &layout, ::text_, tr("Delete Widget")};
WQ<QSpinBox> counter{::layout_, &layout, ::name_, L"counter"};
};
那么,构造函数也很简单:
QWidget_WindowContact::QWidget_WindowContact(QWidget *parent) : QWidget(parent)
{
setMinimumSize(100, 100);
setStyleSheet("border: 1px solid gray");
connect(&btnInc, &QPushButton::clicked, this, &Self::inc_clicked);
connect(&btnDec, &QPushButton::clicked, this, &Self::dec_clicked);
connect(&btnDel, &QPushButton::clicked, this, &Self::close);
}
插槽保持原样,除非您需要进行自省,否则无需实际制作插槽。不要将方法包装在“什么都不做”而只是转发调用的槽中——这是不必要的。还要注意现代 (Qt 5) 风格的信号槽连接的使用。另请注意,当前的 Qt 版本是 Qt 6.1 - 因此现代信号槽语法很难“现代”。
void QWidget_WindowContact::inc_clicked()
{
counter.setValue(counter.value() + 1);
}
void QWidget_WindowContact::dec_clicked()
{
counter.setValue(counter.value() - 1);
}
该示例在外部 window:
内设置了一个 3x3 的小部件网格int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QWidget w;
QGridLayout layout{&w};
constexpr int rows = 3, cols = 3;
QWidget_WindowContact wc[rows * cols];
for (int i = 0; i < rows; ++i)
for (int j = 0; j < cols; ++j)
layout.addWidget(&wc[i * cols + j], i, j);
w.show();
return a.exec();
}
#include "main.moc"
可编译示例到此结束。它看起来像这样: