parent 在 Qt 中的作用是什么?
What is parent for in Qt?
几乎每个 QtWidgets class 都可以有 parent。通常在 object 初始化时设置 parent 是可选的。例如,如果我创建一个 class 继承 QWidget
class,我将在构造函数上执行以下操作:
Widget::Widget(QWidget* parent): QWidget(parent) {
hbox = new QHBoxLayout(this);
yes_button = new QPushButton("&Yes");
no_button = new QPushButton("&No", this);
cancel_button = new QPushButton("&Cancel", hbox);
}
我可以设置也可以不设置parent。我可以将 cancel_button
设置为 hbox
的 child。我也可以将 cancel_button
设置为 yes_button
的 child,但我认为这样做很糟糕。
这有什么意义?而且,真的有必要为我创建的每个基于 QWidget
的 class 设置 parent 吗?
除了有助于 GUI 对象中的绘制顺序外,它还有助于内存管理,因此当您销毁 QObject 时,它的所有子对象也会被销毁。有关详细信息,请参阅 http://doc.qt.io/qt-4.8/objecttrees.html。当父项发生变化时(例如调整大小时),它可以通知其子项也进行自我更新。
要回答您的问题,您不需要为所有内容设置父项(毕竟这就是为什么它是一个可选参数),但大多数时候最好正确设置它。
首先,QWidget
是QObject
,QObject
是QObject
树中的节点。子节点由父节点管理内存,除非您在父节点有机会取消分配它们之前取消分配它们。因此,内存管理是小部件或任何其他 QObject
拥有父级的原因之一。
其次,可见的无父小部件始终 顶级windows。相反,不可能有一个没有父元素的非顶级小部件。当您显示无父小部件时,它会获得自己的 window。相反的情况不一定正确——可以给子部件一个 Qt::Window
标志,并且它也成为顶级 window。
推论是 任何 包含在其他小部件中的小部件 有 父级 - 否则它将是顶级 window。您可能未明确设置此父项,但已设置。
我认为您的问题可以改写为:我什么时候需要显式地为小部件构造函数提供父级?答案是:
只要小部件是您打算拥有父级的顶级 window。这样的 windows 不受布局管理的约束,因此没有为您设置该父级的机制。顶级瞬态对话框需要有父级,以便它们相对于父级正确定位 window.
当您有不受布局管理的子窗口小部件时。
受布局管理约束的小部件在插入到布局时成为父级:
int main(int argc, char ** argv) {
QApplication app(argc, argv);
QWidget window;
QVBoxLayout layout(&window);
QLabel label("Hello");
QPushButton button("Goodbye");
layout.addWidget(&label);
layout.addWidget(&button);
QObject::connect(&button, &QPushButton::clicked, [&app]{ app.quit(); });
window.show();
return app.exec();
}
最后,并非所有小部件或 QObject
都需要在堆上显式创建。由于 Qt 中所有 QObject
派生的 classes(以及许多其他 classes!)都使用 PIMPL 习惯用法,当您在堆上单独分配它们时,您确实在做堆分配两倍。首先,您分配 class 的实例——有时该实例只有一两个指针那么小——然后 class 的构造函数分配其 PIMPL。显式堆分配是一种过早悲观的情况。
为避免这种悲观情绪,您的 Widget
应如下所示:
class Widget : public QWidget {
Q_OBJECT
QHBoxLayout m_layout;
QPushButton m_yesButton, m_noButton, m_cancelButton;
public:
Widget(QWidget * parent = 0);
};
Widget::Widget(QWidget * parent) :
QWidget(parent),
m_layout(this),
m_yesButton("&Yes"),
m_noButton("&No"),
m_cancelButton("&Cancel")
{
m_layout.addWidget(&m_yesButton);
m_layout.addWidget(&m_noButton);
m_layout.addWidget(&m_cancelButton);
}
如果您想使用 the PIMPL idiom,您也可以这样做:
// Widget.h - Interface
class WidgetPrivate;
class Widget : public QWidget {
{
Q_OBJECT
Q_DECLARE_PRIVATE(Widget)
QScopedPointer<WidgetPrivate> const d_ptr;
public:
Widget(QWidget * parent = 0);
~Widget();
};
// Widget.cpp - Implementation
class WidgetPrivate {
Q_DISABLE_COPY(WidgetPrivate)
Q_DECLARE_PUBLIC(Widget)
Widget * const q_ptr;
QHBoxLayout layout;
QPushButton yesButton, noButton, cancelButton;
public:
WidgetPrivate(Widget * q);
};
WidgetPrivate::WidgetPrivate(Widget * q) {
q_ptr(q),
layout(q),
yesButton("&Yes"),
noButton("&No"),
cancelButton("&Cancel")
{
layout.addWidget(&yesButton);
layout.addWidget(&noButton);
layout.addWidget(&cancelButton);
}
Widget::Widget(QWidget * parent) :
QWidget(parent),
d_ptr(new WidgetPrivate(this))
{}
Widget::~Widget() {}
// necessary, since WidgetPrivate is unknown to the interface!
当然,您应该使用 QDialogButtonBox
而不是所有这些:)
几乎每个 QtWidgets class 都可以有 parent。通常在 object 初始化时设置 parent 是可选的。例如,如果我创建一个 class 继承 QWidget
class,我将在构造函数上执行以下操作:
Widget::Widget(QWidget* parent): QWidget(parent) {
hbox = new QHBoxLayout(this);
yes_button = new QPushButton("&Yes");
no_button = new QPushButton("&No", this);
cancel_button = new QPushButton("&Cancel", hbox);
}
我可以设置也可以不设置parent。我可以将 cancel_button
设置为 hbox
的 child。我也可以将 cancel_button
设置为 yes_button
的 child,但我认为这样做很糟糕。
这有什么意义?而且,真的有必要为我创建的每个基于 QWidget
的 class 设置 parent 吗?
除了有助于 GUI 对象中的绘制顺序外,它还有助于内存管理,因此当您销毁 QObject 时,它的所有子对象也会被销毁。有关详细信息,请参阅 http://doc.qt.io/qt-4.8/objecttrees.html。当父项发生变化时(例如调整大小时),它可以通知其子项也进行自我更新。
要回答您的问题,您不需要为所有内容设置父项(毕竟这就是为什么它是一个可选参数),但大多数时候最好正确设置它。
首先,QWidget
是QObject
,QObject
是QObject
树中的节点。子节点由父节点管理内存,除非您在父节点有机会取消分配它们之前取消分配它们。因此,内存管理是小部件或任何其他 QObject
拥有父级的原因之一。
其次,可见的无父小部件始终 顶级windows。相反,不可能有一个没有父元素的非顶级小部件。当您显示无父小部件时,它会获得自己的 window。相反的情况不一定正确——可以给子部件一个 Qt::Window
标志,并且它也成为顶级 window。
推论是 任何 包含在其他小部件中的小部件 有 父级 - 否则它将是顶级 window。您可能未明确设置此父项,但已设置。
我认为您的问题可以改写为:我什么时候需要显式地为小部件构造函数提供父级?答案是:
只要小部件是您打算拥有父级的顶级 window。这样的 windows 不受布局管理的约束,因此没有为您设置该父级的机制。顶级瞬态对话框需要有父级,以便它们相对于父级正确定位 window.
当您有不受布局管理的子窗口小部件时。
受布局管理约束的小部件在插入到布局时成为父级:
int main(int argc, char ** argv) {
QApplication app(argc, argv);
QWidget window;
QVBoxLayout layout(&window);
QLabel label("Hello");
QPushButton button("Goodbye");
layout.addWidget(&label);
layout.addWidget(&button);
QObject::connect(&button, &QPushButton::clicked, [&app]{ app.quit(); });
window.show();
return app.exec();
}
最后,并非所有小部件或 QObject
都需要在堆上显式创建。由于 Qt 中所有 QObject
派生的 classes(以及许多其他 classes!)都使用 PIMPL 习惯用法,当您在堆上单独分配它们时,您确实在做堆分配两倍。首先,您分配 class 的实例——有时该实例只有一两个指针那么小——然后 class 的构造函数分配其 PIMPL。显式堆分配是一种过早悲观的情况。
为避免这种悲观情绪,您的 Widget
应如下所示:
class Widget : public QWidget {
Q_OBJECT
QHBoxLayout m_layout;
QPushButton m_yesButton, m_noButton, m_cancelButton;
public:
Widget(QWidget * parent = 0);
};
Widget::Widget(QWidget * parent) :
QWidget(parent),
m_layout(this),
m_yesButton("&Yes"),
m_noButton("&No"),
m_cancelButton("&Cancel")
{
m_layout.addWidget(&m_yesButton);
m_layout.addWidget(&m_noButton);
m_layout.addWidget(&m_cancelButton);
}
如果您想使用 the PIMPL idiom,您也可以这样做:
// Widget.h - Interface
class WidgetPrivate;
class Widget : public QWidget {
{
Q_OBJECT
Q_DECLARE_PRIVATE(Widget)
QScopedPointer<WidgetPrivate> const d_ptr;
public:
Widget(QWidget * parent = 0);
~Widget();
};
// Widget.cpp - Implementation
class WidgetPrivate {
Q_DISABLE_COPY(WidgetPrivate)
Q_DECLARE_PUBLIC(Widget)
Widget * const q_ptr;
QHBoxLayout layout;
QPushButton yesButton, noButton, cancelButton;
public:
WidgetPrivate(Widget * q);
};
WidgetPrivate::WidgetPrivate(Widget * q) {
q_ptr(q),
layout(q),
yesButton("&Yes"),
noButton("&No"),
cancelButton("&Cancel")
{
layout.addWidget(&yesButton);
layout.addWidget(&noButton);
layout.addWidget(&cancelButton);
}
Widget::Widget(QWidget * parent) :
QWidget(parent),
d_ptr(new WidgetPrivate(this))
{}
Widget::~Widget() {}
// necessary, since WidgetPrivate is unknown to the interface!
当然,您应该使用 QDialogButtonBox
而不是所有这些:)