QPushButton
中有一个QWidget
,click
按钮应打开另一个QWidget
,如下所示:
project.pro
QT += core gui
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
TARGET = untitled
TEMPLATE = app
CONFIG(debug, debug|release) {
DESTDIR = debug
} else {
DESTDIR = release
}
INCLUDEPATH += ..
SOURCES += ../main.cpp\
../widget.cpp \
../secondwidget.cpp
HEADERS += ../widget.h \
../secondwidget.h
widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = 0);
~Widget();
};
#endif // WIDGET_H
widget.cpp
#include "widget.h"
#include <QVBoxLayout>
#include <QPushButton>
#include "secondwidget.h"
Widget::Widget(QWidget *parent)
: QWidget(parent)
{
resize(400, 300);
QVBoxLayout *vLayout = new QVBoxLayout(this);
QPushButton *bt = new QPushButton("Open 2nd Widget");
vLayout->addWidget(bt, 1, Qt::AlignRight);
SecondWidget *secondWidget = new SecondWidget();
// SecondWidget *secondWidget = new SecondWidget(this);
connect(bt, &QPushButton::clicked, secondWidget, &SecondWidget::show);
}
Widget::~Widget()
{
}
secondwidget.h
#ifndef SECONDWIDGET_H
#define SECONDWIDGET_H
#include <QWidget>
class SecondWidget : public QWidget
{
Q_OBJECT
public:
explicit SecondWidget(QWidget *parent = 0);
signals:
public slots:
};
#endif // SECONDWIDGET_H
secondwidget.cpp
#include "secondwidget.h"
SecondWidget::SecondWidget(QWidget *parent) : QWidget(parent)
{
setAttribute( Qt::WA_QuitOnClose, false );
}
将
this
传递给SecondWidget
构造函数(如在注释行中)将在某些时候破坏我的逻辑。因此,单击按钮后,SecondWidget
不再显示。这是怎么回事?
最佳答案
除了析构函数和构造函数的问题(以及由于第二个问题而导致的内存泄漏)以及此处的项目文件,您还可能需要了解一些事情,以了解整个父级情况:
您不需要通过this
。分配父对象的目的是为了简化QObject
实例(包括从QObject
类继承的所有类)的清理过程。 Qt父子模型遵循C ++标准,即按析构函数的相反顺序调用析构函数。简单来说,这意味着:
想象一下,您创建了小部件A,B和C。使用父属性,您将B和C分配为A的子代。因此,这里有创建的顺序:
家长)
B,C(儿童)
现在,您需要销毁小部件(例如:关闭应用程序)。标准的C ++方法是按照以下(与构造相反)的顺序销毁它们:
B,C(儿童)
家长)
如果我们先去找父母,这确实会发生。但是,我们在这里处理Qt,因此我们必须考虑库提供的一项附加功能-插槽和信号。
每当QObject
被销毁时,它都会发出信号。根据对象在父子关系中扮演的角色,结果为以下之一:
只有被销毁的QObject
被销毁-这是在我们销毁其父级之前销毁一个孩子时发生的情况
该QObject
的所有子级都首先被破坏,接着是QObject
本身的破坏-这是父级被破坏时发生的情况。所发射的信号被其所有子级捕获,作为回报,它们也被消除。
但是,分配父母不是必须的。您可以自己手动进行清理。在Qt documentation中了解有关父子模型的更多信息。
除了所有权转移之外(窗口小部件成为另一个窗口小部件的子代)通常会自动发生,因此无需显式指定窗口小部件的父级。再次从Qt documentation中取出内容,这是一个例外。如果将窗口小部件添加到布局,则所有权不会转移到布局本身,而是转移到它的QWidget
中。
最后,有一种重要的情况是,不分配父母会使事情变得非常非常不同。在这里,我将引用QObject
上的Qt documentation:
将parent设置为0会构造一个没有父对象的对象。如果对象是窗口小部件,它将成为顶级窗口。
因此,例如,如果您有一个QWidget
而不将其添加到某个布局(并间接添加到具有该布局的QWidget
中),它将自动成为一个单独的窗口。
编辑:
检查构造函数,尤其是检查secondWidget
对象的方式。如前所述,如果您不想将其分配给父窗口小部件,则需要进行清理。
您为它动态分配内存
SecondWidget *secondWidget = new SecondWidget();
甚至将其连接到您的按钮
connect(bt, &QPushButton::clicked, secondWidget, &SecondWidget::show);
但是,您永远不会使用
delete
释放分配的内存或将其分配给父窗口小部件,这将自动进行处理。因此,您创建了内存泄漏。通过信号和插槽连接QObject
与所有权转移无关。我个人认为您实际上希望
secondWidget
是屏幕上显示的额外窗口。在这种情况下,您需要创建SecondWidget
类型的类成员SecondWidget *secondWidget;
然后在构造函数中分配它,并连接所需的任何插槽和信号
Widget::Widget(QWidget *parent) : QWidget(parent)
{
//...
secondWidget = new SecondWidget();
connect(bt, &QPushButton::clicked, secondWidget, &SecondWidget::show);
}
最后释放构造函数中的内存:
Widget::~Widget()
{
delete secondWidget;
}
否则,就像我说的那样,您基本上是在创建对内存块的引用,并且在离开构造函数之后,由于引用超出范围,该引用将被销毁。
编辑2:
这是一个小示例,如果您希望
secondWidget
作为主窗口小部件的子代,该怎么做:main.cpp
#include "widget.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget w;
w.show();
return a.exec();
}
widget.hpp
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include "secondwidget.h"
namespace Ui {
class Widget;
}
class Widget : public QWidget
{
Q_OBJECT
public:
explicit Widget(QWidget *parent = 0);
~Widget();
private:
Ui::Widget *ui;
};
#endif // WIDGET_H
widget.cpp
#include "widget.h"
#include "ui_widget.h"
Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this);
SecondWidget *secondWidget = new SecondWidget(this); # secondWidget is now officially adopted by Widget
# If you skip the parent assignment inside SecondWidget you can call secondWidget->setParent(this) here
connect(ui->pushButton, SIGNAL(clicked(bool)), secondWidget, SLOT(show()));
}
Widget::~Widget()
{
delete ui;
}
secondwidget.hpp
#ifndef SECONDWIDGET_H
#define SECONDWIDGET_H
#include <QObject>
#include <QDialog>
class SecondWidget : public QDialog
{
Q_OBJECT
public:
explicit SecondWidget(QWidget *parent = 0);
~SecondWidget();
};
#endif // SECONDWIDGET_H
secondwidget.cpp
#include "secondwidget.h"
#include <QFormLayout>
#include <QLabel>
SecondWidget::SecondWidget(QWidget *parent) : QDialog(parent)
{
# If you don't call the constructor of your superclass you can still assign a parent by invoking setParent(parent) here
QFormLayout *layout = new QFormLayout();
QLabel *label = new QLabel("SecondWidget here");
layout->addWidget(label); # Transfer ownership of label to SecondWidget
setLayout(layout);
}
SecondWidget::~SecondWidget()
{
}
注意
setParent(parent)
的构造函数中的SecondWidget
。您必须调用超类构造函数(如已完成)或从主窗口小部件的构造函数中手动调用setParent(parent). If you don't do that your
secondWidget will not be assigned as a child to your main widget where you create it and thus you will produce a memory leak. You can also invoke
secondWidget-> setParent(this)`来设置父级。您可以通过以下方法检查父子层次结构是否正常:
为每个拥有的
QObject
(QWidget
,QLayout
等都是QObject
的所有子类)使用QObject::setObjectName("some name")
分配对象名称在两个构造函数的末尾添加:
for(int i = 0; i < this->children().count(); i++)
std::cout << this->children()[i]->objectName().toStdString() << std::endl; // Add #include <iostream> to get the output
它基本上遍历
this
(Widget
或SecondWidget
)的所有子级并显示其子级。就我而言 label // Printing out children of SecondWidget
formLayout // Printing out children of SecondWidget
gridLayout // Printing out children of Widget
pushButton // Printing out children of Widget
main second widget // Printing out children of Widget
一旦启动应用程序。
编辑3:啊,我没有注意到您正在
QWidget(parent)
构造函数中调用SecondWidget
。这也可以解决问题,因此您不需要setParent(parent)
。我已经更改了第二个编辑。