目录
QWidget基础项目文件解析
.pro文件解析
widget.h文件解析
widget.cpp文件解析
widget.ui文件解析
main.cpp文件解析
认识对象模型
窗口坐标系
QWidget基础项目文件解析
.pro文件解析
工程新建好之后,在工程目录列表中有⼀个后缀为 ".pro" 的文件, ".pro" 文件就是工程文件 (project),它是 qmake 自动生成的用于生产 makefile 的配置文件。
双击进入该文件,该文件的核心内容如下:
QT += core gui
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
CONFIG += c++11
DEFINES += QT_DEPRECATED_WARNINGS
TARGET = QtTest
TEMPLATE = app
SOURCES += \
main.cpp \
widget.cpp
HEADERS += \
widget.h
FORMS += \
widget.ui
- 注释:从"#"开始,直到该行结束
- QT += core gui:Qt包含的模块
- greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
这条语句的含义是,若QT_MAJOR_VERSION大于4,即当前使用的Qt5及更高版本,需要增加widgets模块。若项目仅需支持Qt5,也可以直接添加"QT += widgets"⼀句。不过为了保持代码兼容,最好还是按照Qt Creator生成的语句编写。
- CONFIG += c++11
CONFIG用来告诉qmake关于应用程序的配置信息,上述语句说明使用c++11的特性
- DEFINES += QT_DEPRECATED_WARNINGS
定义编译选项。QT_DEPRECATED_WARNINGS表示当Qt的某些功能被标记为过时的,那么编译器会发出警告。
- TARGET = QtTest:指定生成的应用程序名
- TEMPLATE = app:告诉qmake为这个应用程序生成哪种makefile
app | 建立一个应用程序的makefile,默认值,若模板没有被指定,这个将会被使用 |
lib | 建立一个库的makefile |
vcapp | 建立一个应用程序的VisualStudio项目文件 |
vclib | 建立一个库的VisualStudio项目文件 |
subdirs | 这是一个特殊的模板。它可以创建一个能够进入特定目录的makefile并且为它调用make的makefile |
- 工程中包含的源文件:SOURCES += main.cpp/widget.cpp
- 工程中包含的头文件:HEADERS += widget.h
- 工程中包含的资源文件:RESOURCES += painter.qrc
- 工程中包含的"ui"设计文件:FORMS += widget.ui
widget.h文件解析
在Qt中,若要使用信号与槽(signal和slot)的机制就必须加入Q_OBJECT宏。
Ui::Widget *ui;这个指针是用前面声明的namespace Ui中的Widget类定义的(不是同一个Widget),所以指针ui指向可视化设计的界面,后面要访问界面上的组件,都需要通过这个指针ui去访问。
Ui:Widget继承自Ui_Widget类,Ui_Widget类位于ui_widget.h文件中,ui_widget.h文件是对widget.ui文件编译后自动生成的。
widget.cpp文件解析
widget.cpp文件是类Widget的实现代码,所有在窗体上要实现的功能添加在此文件中
widget.ui文件解析
widget.ui是窗体界面定义文件,是一个XML文件,定义了窗口上的所有组件的属性设置、布局,及其信号与槽函数的关联等。用UI设计器可视化设计的界面都由Qt自动解析,并以XML文件的形式保存下来。在设计界面时,只需在UI设计器里进行可视化设计即可,而不用管widget.ui文件是怎么生成的。
main.cpp文件解析
使用 Qt Creator 新建任意工程之后,main.cpp 文件中都会自动生成如下代码:
- Qt 系统提供的标准类名声明头文件没有 .h 后缀
- Qt ⼀个类对应⼀个头文件,类名就是头文件名
- QApplication 为应用程序类,QApplication a;(a为应用程序对象,有且仅有⼀个。)
- QApplication 管理图形用户界面应用程序的控制流和主要设置
- QApplication 是 Qt 的整个后台管理的命脉。它包含主事件循环,在其中来自窗口系统和其它资源的所有事件处理和调度。它也处理应用程序的初始化和结束,并且提供对话管理。
- 对于任何⼀个使用 Qt 的图形用户界面应用程序,都正好存在⼀个 QApplication 对象,而不论这个应用程序在同⼀时间内是不是有 0、1、2 或更多个窗口。
- Widget w; //实例化窗口对象
- w.show(); //调用show函数显示窗口
- a.exec() //程序进入消息循环,等待对用户输入进行响应。这里 main() 把控制权转交给Qt,Qt 完成事件处理工作,当应用程序退出的时候 exec() 的值就会返回。在 exec() 中,Qt 接受并处理用户和系统的事件并且把它们传递给适当的窗口部件。
认识对象模型
在Qt中创建很多对象的时候会提供一个parent对象指针,这个parent到底是干什么的呢?
QObject是以对象树的形式组织起来的
- 当创建一个QObject对象时,会看到QObject的构造函数接收⼀个QObject指针作为参数,这个参数就是parent,即父对象指针
- 在创建QObject对象时,可以提供⼀个其父对象,创建的这个QObject对象会自动添加到其父对象的children()列表
- 当父对象析构的时候,这个列表中的所有对象也会被析构(这里的父对象并不是继承意义上的父类)
这种机制在GUI程序设计中相当有用。如,一个按钮有一个QShortcut(快捷键)对象作为其子对象。当删除按钮的时候,这个快捷键理应被删除,这是合理的。
QWidget是能够在屏幕上显示的一切组件的父类
- QWidget继承自QObject,因此也继承了这种对象树关系。一个孩子自动地成为父组件的⼀个子组件。因此,它会显示在父组件的坐标系统中,被父组件的边界剪裁。如:当用户关闭⼀个对话框的时候,应用程序将其删除,我们希望属于这个对话框的按钮、图标等应该⼀起被删除。因为这些都是对话框的子组件。
- 我们也可以自己删除子对象,它们会自动从其父对象列表中删除。如:当删除了一个工具栏时,其所在的主窗口会自动将该工具栏从其子对象列表中删除,并且自动调整屏幕显示。
对象树机制一定程度上解决了内存问题
- 当一个QObject对象在堆上创建的时候,Qt会同时为其创建一个对象树。不过对象树中对象的顺序是没有定义的,即销毁这些对象的顺序也是未定义的
- 任何对象树中的QObject对象delete时,若这个对象有parent,则自动将其从parent的children()列表中删除;若有孩子,则自动delete每一个孩子。Qt保证没有QObject会被delete两次,这是由析构顺序决定的
若QObject在栈上创建,Qt保持同样的行为。正常情况下也不会发生什么问题
作为父组件的window和作为子组件的quit都是QObject的子类。这段代码是正确的,quit的析构函数不会被调用两次,因为标准C++要求,局部对象的析构顺序应该按照其创建顺序的相反过程。因此,这段代码在超出作用域时,会先调用quit的析构函数,将其从父对象window的子对象列表中删除,然后才会再调用window的析构函数
上述代码的析构顺序就有了问题。在上面的代码中,作为父对象的window会先被析构,因为它是最后一个创建的对象。在析构过程中,它会调用子对象列表中每一个对象的析构函数,即quit此时就被析构了。然后代码继续执行,在window析构之后,quit也会被析构,因为quit也是⼀个局部变量,在超出作用域的时候当然也需要析构。但是,这时候已经是第⼆次调用quit的析构函数了,C++不允许调用两次析构函数,因此,程序崩溃了。
自定义MyPushButton类观察释放过程
mypushbutton.h:
#ifndef MYPUSHBUTTON_H
#define MYPUSHBUTTON_H
#include <QPushButton>
#include <QDebug>
class MyPushButton : public QPushButton
{
Q_OBJECT
public:
explicit MyPushButton(QWidget *parent = nullptr);
~MyPushButton();//添加MyPushButton类的析构函数
signals:
};
#endif // MYPUSHBUTTON_H
mypushbutton.cpp:
#include "mypushbutton.h"
MyPushButton::MyPushButton(QWidget *parent) : QPushButton(parent)
{
qDebug() << "我的按钮的构造函数被调用";
}
MyPushButton::~MyPushButton()
{
qDebug() << "我的按钮的析构函数被调用";
}
widget.cpp:
#include "widget.h"
#include "ui_widget.h"
#include "mypushbutton.h"
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
MyPushButton* button = new MyPushButton(this);
//传入this,将其设置到对象树中,当窗口关闭时就会自动调用其析构函数
button->setText("我的按钮");
}
Widget::~Widget()
{
qDebug() << "Widget的析构函数被调用";
delete ui;
}
对象树确保的是先释放子节点的内存,后释放父节点的内存。而析构函数的调用顺序则不⼀定遵守上述要求,因此看到子节点的析构执行顺序反而在父节点析构顺序之后
注意:调用析构函数和释放内存并非是同一件事情
总结:
Qt的对象树机制虽然在⼀定程度上解决了内存问题,但是也引入了⼀些值得注意的事情。这些细节在今后的开发过程中很可能时不时跳出来烦扰一下,所以最好从开始就养成良好习惯
注意:在Qt中,尽量在构造的时候就指定parent对象,并且大胆在堆上创建
窗口坐标系
坐标体系:以左上角度为原点(0,0),X向右增加,Y向下增加
对于嵌套窗口,其坐标是相对于父窗口而言的
可以使用代码设置控件在坐标系中的位置
#include "widget.h"
#include "ui_widget.h"
#include "mypushbutton.h"
#include <QPushButton>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
QPushButton* btn1 = new QPushButton("按钮1", this);
btn1->move(200, 300);
QPushButton* btn2 = new QPushButton("按钮2", this);
qDebug() << "按钮1的坐标为:[" << btn1->x() << "," << btn1->y() << "]";
qDebug() << "按钮2的坐标为:[" << btn2->x() << "," << btn2->y() << "]";
}
Widget::~Widget()
{
delete ui;
}
200与300都代表着像素点