1.Qt概述
什么是Qt
不论我们学习什么样的知识点首先第一步都需要搞明白它是什么,这样才能明确当前学习的方向是否正确,下面给大家介绍一下什么是Qt。
Qt是一个跨平台的C++应用程序开发框架
- 具有短平快的优秀特质: 投资少、周期短、见效快、效益高
- 几乎支持所有的平台, 可用于桌面程序开发以及嵌入式开发
- 有属于自己的事件处理机制
- 可以搞效率的开发基于窗口的应用程序。
Qt是标准C++的扩展, C++的语法在Qt中都是支持的
- 良好封装机制使得Qt的模块化程度非常高,可重用性较好,可以快速上手。
- Qt提供了一种称为 signals/slots 的安全类型来替代 callback(回调函数),这使得各个元件 之间的协同工作变得十分简单。
Qt的特点
知道了Qt是什么之后,给大家介绍一下Qt这个框架的一些优点,就是因为具有了这些优秀的特质才使得现在很多企业都首选Qt进行基于窗口的应用程序开发,并且近年来市场对Qt程序猿的需求也在不断攀升。
广泛用于开发GUI程序,也可用于开发非GUI程序。
有丰富的 API
- Qt 包括多达250个以上的C++ 类
- 可以处理正则表达式。
支持 2D/3D 图形渲染,支持 OpenGL
Qt给程序猿提供了非常详细的官方文档
支持XML,Json
框架底层模块化, 使用者可以根据需求选择相应的模块来使用
可以轻松跨平台
- 和Java的跨平台方式不同
- 在不同的平台使用的是相同的上层接口,但是在底层封装了不同平台对应的API。
Qt中的模块
Qt类库里大量的类根据功能分为各种模块,这些模块又分为以下几大类:
Qt 基本模块(Qt Essentials):提供了 Qt 在所有平台上的基本功能。
Qt 附加模块(Qt Add-Ons):实现一些特定功能的提供附加价值的模块。
增值模块(Value-AddModules):单独发布的提供额外价值的模块或工具。
技术预览模块(Technology Preview Modules):一些处于开发阶段,但是可以作为技术预览使用的模块。
Qt 工具(Qt Tools):帮助应用程序开发的一些工具。
2.第一个Qt项目
创建项目
创建基于窗口的Qt应用程序
指定项目的存储路径
- 项目名称根据需求自己指定即可
- 在指定项目的存储路径的时候, 路径中不能包含中文
指定默认的窗口类的名字以及窗口的类型
选择编译套件, 编译套件用于项目文件的编译, 如果安装了多个编译套件, 在这里选择其中一个就可以了。
选择版本控制工具, 比如: git, svn 等, 可以不指定。
项目文件
在创建的Qt项目中自动生成了一个后缀为 .pro 的项目文件,该文件中记录着项目的一些属性信息,具体信息如下:
# 在项目文件中, 注释需要使用 井号(#)
# 项目编译的时候需要加载哪些底层模块
QT += core gui
# 如果当前Qt版本大于4, 会添加一个额外的模块: widgets
# Qt 5中对gui模块进行了拆分, 将 widgets 独立出来了
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
# 使用c++11新特性
CONFIG += c++11
#如果在项目中调用了废弃的函数, 项目编译的时候会有警告的提示
DEFINES += QT_DEPRECATED_WARNINGS
# 项目中的源文件
SOURCES += \
main.cpp \
mainwindow.cpp
# 项目中的头文件
HEADERS += \
mainwindow.h
# 项目中的窗口界面文件
FORMS += \
mainwindow.ui
main.cpp
在这个源文件中有程序的入口函数main(),下面给大家介绍下这个文件中自动生成的几行代码:
#include "mainwindow.h" // 生成的窗口类头文件
#include <QApplication> // 应用程序类头文件
int main(int argc, char *argv[])
{
// 创建应用程序对象, 在一个Qt项目中实例对象有且仅有一个
// 类的作用: 检测触发的事件, 进行事件循环并处理
QApplication a(argc, argv);
// 创建窗口类对象
MainWindow w;
// 显示窗口
w.show();
// 阻塞函数,应用程序对象开始事件循环, 保证应用程序不退出
return a.exec();
}
mainwindow.ui
在Qt中每一个窗口都对应一个可编辑的可视化界面(*.ui), 这个界面对应的是一个xml格式的文件, 一般情况下不需要在xml格式下对这个文件进行编辑, 关于这个文件结构了解即可。
<!-- 双击这个文件看到的是一个窗口界面, 如果使用文本编辑器打开看到的是一个XML格式的文件 -->
<!-- 看不懂这种格式没关系, 我们不需要在这种模式下操作这个文件。 -->
<!-- 这里只是给大家介绍这个文件的本质 -->
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>600</height>
</rect>
</property>
<property name="windowTitle">
<string>MainWindow</string>
</property>
<widget class="QWidget" name="centralwidget"/>
<widget class="QMenuBar" name="menubar"/>
<widget class="QStatusBar" name="statusbar"/>
</widget>
<resources/>
<connections/>
</ui>
mainwindow.h
这个文件是窗口界面对应的类的头文件。
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow> // Qt标准窗口类头文件
QT_BEGIN_NAMESPACE
// mainwindow.ui 文件中也有一个类叫 MainWindow, 将这个类放到命名空间 Ui 中
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT // 这个宏是为了能够使用Qt中的信号槽机制
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private:
Ui::MainWindow *ui; // 定义指针指向窗口的UI对象
};
#endif // MAINWINDOW_H
mainwindow.cpp
这个文件是窗口界面对应的类的源文件。
#include "mainwindow.h"
#include "ui_mainwindow.h"
//构造函数
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow) // 基于mainwindow.ui创建一个实例对象
{
// 将 mainwindow.ui的实例对象和当前类的对象进行关联
// 这样同名的两个类对象就产生了关联, 合二为一了
ui->setupUi(this);
}
//析构函数
MainWindow::~MainWindow()
{
delete ui;
}
3.Qt中的窗口类
我们在通过Qt向导窗口基于窗口的应用程序的项目过程中倒数第二步让我们选择跟随项目创建的第一个窗口的基类, 下拉菜单中有三个选项, 分别为: QMainWindow、QDialog、QWidget如下图:
-
常用的窗口类有3个
- 在创建Qt窗口的时候, 需要让自己的窗口类继承上述三个窗口类的其中一个
-
QWidget
-
所有窗口类的基类
-
Qt中的控件(按钮, 输入框, 单选框…)也属于窗口, 基类都是QWidget
-
可以内嵌到其他窗口中: 没有边框
-
可以不内嵌单独显示: 独立的窗口, 有边框
-
-
QDialog
-
对话框类, 后边的章节会具体介绍这个窗口
-
不能内嵌到其他窗口中
-
-
QMainWindow
-
有工具栏, 状态栏, 菜单栏, 后边的章节会具体介绍这个窗口
-
不能内嵌到其他窗口中
-
QWidget
新建一个Qt设计师界面类
窗口的显示
-
内嵌窗口
- 依附于某一个大的窗口, 作为了大窗口的一部分
- 大窗口就是这个内嵌窗口的父窗口
- 父窗口显示的时候, 内嵌的窗口也就被显示出来了
-
不内嵌窗口
-
这类窗口有边框, 有标题栏
-
需要调用函数才可以显示
-
编写mainwindows.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "testwidget.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
//一般在qt的构造函数中进行初始化操作
//显示当前窗口的时候,显示另外一个窗口TestWidget
//创建窗口对象,没有给W对象指定父对象
TestWidget* w = new TestWidget;
w->show();
}
MainWindow::~MainWindow()
{
delete ui;
}
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
//一般在qt的构造函数中进行初始化操作
//显示当前窗口的时候,显示另外一个窗口TestWidget
#if 0
//创建窗口对象,没有给W对象指定父对象
//要显示这个窗口必须要进行show()操作
TestWidget* w = new TestWidget;
w->show();
#else
#endif
#if 1
// 创建窗口对象,给W对象指定父对象
// explicit TestWidget(QWidget *parent = nullptr);
// 如果创建一个窗口对象的时候给其指定了父对象,这个窗口就不是一个独立窗口
// 这样的话当前父窗口显示的时候,子窗口就一并被显示出来了
// 这时候子窗口是没有边框的
TestWidget* w = new TestWidget(this);
#else
#endif
}
QDialog
创建一个QDiglog界面类
非模态显示
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
//一般在qt的构造函数中进行初始化操作
//显示当前窗口的时候,显示另外一个窗口TestWidget
#if 1
//创建窗口对象,没有给W对象指定父对象
//要显示这个窗口必须要进行show()操作
TestWidget* w = new TestWidget;
w->show();
#else
// 创建窗口对象,给W对象指定父对象
// explicit TestWidget(QWidget *parent = nullptr);
// 如果创建一个窗口对象的时候给其指定了父对象,这个窗口就不是一个独立窗口
// 这样的话当前父窗口显示的时候,子窗口就一并被显示出来了
// 这时候子窗口是没有边框的
TestWidget* w = new TestWidget(this);
#endif
//创建对话框窗口
Dialog *dlg = new Dialog();
//非模态
dlg->show();
}
模态显示
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
//一般在qt的构造函数中进行初始化操作
//显示当前窗口的时候,显示另外一个窗口TestWidget
#if 1
//创建窗口对象,没有给W对象指定父对象
//要显示这个窗口必须要进行show()操作
TestWidget* w = new TestWidget;
w->show();
#else
// 创建窗口对象,给W对象指定父对象
// explicit TestWidget(QWidget *parent = nullptr);
// 如果创建一个窗口对象的时候给其指定了父对象,这个窗口就不是一个独立窗口
// 这样的话当前父窗口显示的时候,子窗口就一并被显示出来了
// 这时候子窗口是没有边框的
TestWidget* w = new TestWidget(this);
#endif
#if 0
//创建对话框窗口
Dialog *dlg = new Dialog();
//非模态
dlg->show();
#else
//创建对话框窗口
Dialog *dlg = new Dialog();
//模态,exec()
//阻塞程序的执行
dlg->exec();
#endif
}
运行后发现只能拖动Dialog的窗口,主窗口没有显示,因为Dialog被阻塞了,而主窗口需要在构造函数初始化结束才会显示。
4.坐标体系
在Qt关于窗口的显示是需要指定位置的,这个位置是通过坐标来确定的,所有坐标的选取又都是基于坐标原点来确定的,关于这些细节的确定,下面依次给大家进行讲解。
窗口的坐标原点
所有坐标的确定都需要先找到坐标原点, Qt的坐标原点在窗口的左上角
- x轴向右递增
- y轴向下递增
窗口的相对坐标
在一个Qt窗口中一般都有很多子窗口内嵌到这个父窗口中,其中每个窗口都有自己的坐标原点,子窗口的位置也就是其使用的坐标点就是它的父窗口坐标体系中的坐标点。
-
在Qt的某一个窗口中有可能有若干个控件, 这个控件都是嵌套的关系
- A窗口包含B窗口, B窗口包含C窗口
-
每个窗口都有坐标原点, 在左上角
- 子窗口的位置是基于父窗口的坐标体系来确定的, 也就是说通过父窗口左上角的坐标点来确定自己的位置
-
Qt中窗口显示的时候使用的相对坐标, 相对于自己的父窗口
-
将子窗口移动到父窗口的某个位置
示例:
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QPushButton>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
this->move(10,10);
// 创建一个按钮,让这个按钮作为当前创建的子控件
QPushButton* btnA = new QPushButton(this);
// 移动按钮的位置
btnA->move(10,10);
// 给按钮设置固定大小
btnA->setFixedSize(200,200);
// 创建第二个按钮,让这个按钮作为当前创建的子控件
QPushButton* btnB = new QPushButton(btnA);
// 移动按钮的位置
btnB->move(10,10);
// 给按钮设置固定大小
btnB->setFixedSize(100,100);
// 创建第二个按钮,让这个按钮作为当前创建的子控件
QPushButton* btnC = new QPushButton(btnB);
// 移动按钮的位置
btnC->move(10,10);
// 给按钮设置固定大小
btnC->setFixedSize(50,50);
}
内存回收
在Qt中创建对象的时候会提供一个 Parent对象指针
(可以查看类的构造函数),下面来解释这个parent到底是干什么的。
QObject
是以对象树的形式组织起来的。当你创建一个QObject对象时,会看到QObject的构造函数接收一个QObject指针作为参数,这个参数就是 parent,也就是父对象指针。
这相当于,在创建QObject
对象时,可以提供一个其父对象,我们创建的这个QObject
对象会自动添加到其父对象的children()列表。当父对象析构的时候,这个列表中的所有对象也会被析构。(注意,这里的父对象并不是继承意义上的父类!
)
QWidget
是能够在屏幕上显示的一切组件的父类。QWidget
继承自QObject
,因此也继承了这种对象树关系。一个孩子自动地成为父组件的一个子组件。因此,它会显示在父组件的坐标系统中,被父组件的边界剪裁。例如,当用户关闭一个对话框的时候,应用程序将其删除,那么,我们希望属于这个对话框的按钮、图标等应该一起被删除。事实就是如此,因为这些都是对话框的子组件。
Qt 引入对象树的概念,在一定程度上解决了内存问题。
当一个
QObject
对象在堆上创建的时候,Qt 会同时为其创建一个对象树。不过,对象树中对象的顺序是没有定义的。这意味着,销毁这些对象的顺序也是未定义的。任何对象树中的
QObject
对象 delete 的时候,如果这个对象有 parent,则自动将其从 parent 的children()列表中删除;如果有孩子,则自动 delete 每一个孩子。Qt 保证没有QObject
会被 delete 两次,这是由析构顺序决定的。
综上所述, 我们可以得到一个结论: Qt中有内存回收机制, 但是不是所有被new出的对象被自动回收, 满足条件才可以回收
, 如果想要在Qt中实现内存的自动回收, 需要满足以下两个条件:
- 创建的对象必须是QObject类的子类(间接子类也可以)
- QObject类是没有父类的, Qt中有很大一部分类都是从这个类派生出去的
- Qt中使用频率很高的窗口类和控件都是 QObject 的直接或间接的子类
- 创建出的类对象, 必须要指定其父对象是谁, 一般情况下有两种操作方式:
// 方式1: 通过构造函数
// parent: 当前窗口的父对象, 找构造函数中的 parent 参数即可
QWidget::QWidget(QWidget *parent = Q_NULLPTR, Qt::WindowFlags f = Qt::WindowFlags());
QTimer::QTimer(QObject *parent = nullptr);
// 方式2: 通过setParent()方法
// 假设这个控件没有在构造的时候指定符对象, 可以调用QWidget的api指定父窗口对象
void QWidget::setParent(QWidget *parent);
void QObject::setParent(QObject *parent);
测试
//SubWindows.cpp
#include "subwindows.h"
#include "ui_subwindows.h"
#include <QDebug>
SubWindows::SubWindows(QWidget *parent) :
QDialog(parent),
ui(new Ui::SubWindows)
{
ui->setupUi(this);
}
SubWindows::~SubWindows()
{
qDebug() << "我是SubWindows,我被析构了";
delete ui;
}
//MainWindows.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "subwindows.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
SubWindows* sub = new SubWindows(this);
sub->show();
}
MainWindow::~MainWindow()
{
delete ui;
}
关闭主窗口后,观察终端