目录
- 前言
- [一、Qt Hello World 程序](#一、Qt Hello World 程序)
-
- [1、通过图形化方式创建hello world(Label)](#1、通过图形化方式创建hello world(Label))
- [2、通过纯代码方式实现hello world程序(Label)](#2、通过纯代码方式实现hello world程序(Label))
- [3、使用图形化方式创建hello world(输入框)](#3、使用图形化方式创建hello world(输入框))
- [4、通过纯代码方式实现hello world程序(输入框)](#4、通过纯代码方式实现hello world程序(输入框))
- [5、使用图形化方式创建hello world(按钮)](#5、使用图形化方式创建hello world(按钮))
- [6、通过纯代码方式实现hello world程序(按钮)](#6、通过纯代码方式实现hello world程序(按钮))
- 二、对象树
- 三、Qt坐标
前言
【Qt】Qt背景与环境搭建详情请点击查看,今天继续来认识Qt----Qt初始
一、Qt Hello World 程序
1、通过图形化方式创建hello world(Label)
babel(标签):界面上用来显示内容的字符串控件
运行程序结果如下图所示,我们实现了用Label控件使用图形化方式创建了hello world程序
- Qt Designer右上角通过树形结构显示出来了当前界面上都有哪些控件

- 点击编辑查看.ui可以看到刚才往界面上拖拽一个QLabel控件。此时在ui文件的xml中多出来了一段代码。qmake会在编译项目时基于这个内容生成一段C++代码,通过这个代码构建出界面内容

- 鼠标右击.pro文件,选择在Explorer中显示,找到hello world生成的build文件夹,点击进去找到ui_widget.h,这个文件就是qmake根据ui文件的xml文件内容自动生成的代码的头文件


- 查看ui_widget.h文件,可以看到这个文件里面创建了QLabel控件,控件名字、位置,以及控件中的内容hello world

2、通过纯代码方式实现hello world程序(Label)
- 首先还是创建一个空项目,虽然这个空项目还是生成了ui文件,但是我们不使用这个ui图像化创建hello world程序
- 使用纯代码方式构建界面时,通常把构造界面的代码放在widget.cpp文件的构造函数中来实现

- 和图形化构建是一样的,需要创建一个Label控件,在Qt中Label叫QLabel,使用QLabel需要包含头文件#include < QLabel >
- 创建一个Label控件,我们使用new的方式在堆上创建而不使用在栈上创建的方式,具体原因在后面对象树的时候进行说明
- 同时在new的方式的时候,传入this,给当前label对象指定一个"父对象",在这里的this就是main函数里面的widget对象

- 给label控件设置文本使用函数setText,传入需要显示的文本,在下面图片中可以看到,传入的是一个const QString& 的对象,因为Qt诞生的早,当时C/C++的有些东西不太好用,因此自己开发了一系列的基础类(QString、QVector、QList、QMap等)

cpp
#include <QLabel>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
// 在堆上创建
QLabel* label = new QLabel(this);
//QLabel label(this); // 在栈上创建
//将C类型的字符串隐式类型转换成QString类型字符串
label->setText("hello world");
//显示构造一个QString类型的字符串
//label->setText(QString("hello world"));
}
运行结果如下图所示,在窗口的左上角,已经出现了hello world(使用纯代码方式,默认就是在左上角)

问题:我们使用new创建了一个QLabel类,不需要delete释放吗?不会造成内存泄露吗?
- 答案是不会,在Qt中不会产生内存泄露。label对象会在合适的时候被析构释放,原因是将这个对象挂到了对象树上
3、使用图形化方式创建hello world(输入框)
输入框又叫编辑框:我们使用编辑框来实现hello world,编辑框分为单行编辑框(QLineEdit)和多行编辑框(QTextEdit),由于hello world字符串较短,因此我们使用单行编辑框来实现
创建一个新项目,点击.ui文件,在Qt内置控件框中找到QLineEdit控件,拖入到窗口处,在编辑框输入hello world

运行结果如下图所示,实现了在编辑框写入了hello world,且编辑框能编辑

4、通过纯代码方式实现hello world程序(输入框)
和通过纯代码方式实现hello world程序(Label)一样,创建一个QLineEdit* 对象,并使用setText在对象中输入hello world,同样需要包含头文件#include < QLineEdit >
cpp
#include <QLineEdit>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
QLineEdit* edit = new QLineEdit(this);
edit->setText("hello world");
}

5、使用图形化方式创建hello world(按钮)
- 创建一个新项目,点击.ui文件,在Qt内置控件框中找到Push Button控件,拖入到窗口处,输入hello world

结果如下图所示
现在我们实现的按钮点击没有任何效果,但是按钮控件肯定是通过点击出现不同的效果,如果实现呢?(Qt中信号槽机制,后面详细讲解)
- 本质给按钮的点击操作关联上一个处理函数,当用户点击的时候就会执行这个函数
- Qt中的connect是QObject这个类提供的静态函数,这个函数的作用就是"连接信号和槽":
connect(ui->pushButton,&QPushButton::clicked, this, &Widget::handleClick)ui->pushButton访问到form file(ui)文件中创建的控件,谁发出的信号&QPushButton::clicked,点击按钮时会自动触发这个信号,发出的是个什么信号this,谁来处理这个信号&Widget::handleClick:具体怎么处理,需要我们自己实现
- handleClick函数实现,首先在widget.h文件中声明函数,再在widget.cpp文件中实现这个函数,test函数是获取控件中的内容,当按钮点击前是"hello world",那么点击后切换成"hello qt",如果按钮点击前是"hello qt",那么点击后切换成"hello world"
最终运行代码,实现了按钮切换功能
6、通过纯代码方式实现hello world程序(按钮)
- 和前面纯代码实现hello world程序一样,先定义出一个控件对象,写入"hello world"
cpp
QPushButton* myButton = new QPushButton(this);
myButton->setText("hello world");
connect(myButton, &QPushButton::clicked, this, &Widget::handleClick);
- 和图形化实现一样我们实现handleClick函数,但是我们传入myButton时发现,该函数不认识myButton,因为myButton定义在构造函数中,属于局部变量,因此我们需要将这个局部变量定义成widget的成员变量

二、对象树
在Qt中创建很多对象的时候会提供一个Parent对象指针,这个parent到底是干什么的呢?
- QObject是以对象树的形式组织起来的
- 当创建⼀个QObject对象时,会看到QObject的构造函数接收⼀个QObject指针作为参数,这个参数就是parent,也就是父对象指针
- 这相当于,在创建QObject对象时,可以提供⼀个其父对象,我们创建的这个QObject对象会自动添加到其父对象的children()列表
- 当父对象析构的时候,这个列表中的所有对象也会被析构。(注意,这里的父对象并不是继承意义上的父类!)
- 使用对象树把这些内容组织起来,最主要的目的就是为了能在合适的时机(窗口关闭销毁的时候)把这些对象统一进行释放
- 如果不统一释放(窗口关闭销毁的时候),某些控件提前释放,会导致对应控件在界面上就不存在了
在Qt中,尽量在构造的时候就指定parent对象,并且大胆在堆上创建(通过new方式创建对象本质也是将这个对象的声明周期交给Qt的对象树来统一管理)

下面我将演示在栈上使用Label来创建对象的方式的效果
cpp
QLabel label(this); // 在栈上创建
label.setText("hello world");
运行效果如下图所示:代码能运行,但是无法显示出hello world,这是因为
label对象随着构造函数Widget::Widget(QWidget *parent)的结束销毁了
1、对象树自动销毁过程代码演示
- 为了演示对象树自动销毁析构的过程,我们自定义一个类(MyLabel)----新建项目,选择C++ ----> C++Class

- 定义我们自定义的类的名字并选择自定义类继承Qt内置的哪个类,然后点击完成


- 点击mylabel.h,发现在mylabel.h中不认识QLabel,需要包含QLabel头文件

- 对于Qt自动生成的MyLabel构造函数,我们需要进行修改,不使用无参的构造函数,使用带QWidget*参数的构造函数,传入父对象,模拟对象树,这样才能确保自己的类能够加到对象树上。
- 在对应.cpp文件中具体实现构造函数,只需要继承QLabel的构造函数即可

- 自定义一个析构函数,并在析构函数里面打印,这样就能观察到对象树是否最后会自动调用析构函数完成资源的释放。
cpp
MyLabel::~MyLabel()
{
std::cout << "~MyLabel 已调用" << std::endl;
}
- 在这里注意,我们不能使用C++ 的cout来打印:~MyLabel 已调用这个语句。从下面可以看出,在将窗口关闭之后,程序输出了相关语句,析构函数在窗口关闭之后,自动调用了析构函数,但是使用cout打印中文出现了乱码问题

乱码原因:编码方式不匹配。目前表示汉字字符集主要两种方式:
- GBK(中国大陆):使用两字节表示一个汉字(Windows简体中文默认字符集就是GBK)
- UTF-8/utf8(变长编码):表示一个符号,使用的字节数有变化,一个汉字2-4个字节表示,但是在utf8中一般是3个字节表示一个汉字
- 在widget.cpp中需要定义我们MyLabel类对象,通过MyLabel类事项label标签的hello world的打印

- 上面cout打印出现了乱码问题,Qt提供了一个qDebug()工具,借助这个工具与完成日志打印的过程,很好处理字符编码。使用qDebug(),我们需要包含QDebug头文件,注意头文件与qDebug()的大小写区别。
- QDebug是Qt中的一个类,但是我们不会直接去使用这个类,qDebug()是一个宏,封装了QDebug对象,直接使用qDebug(),这个就是C++中的cout的作用

- 从下面运行结果可以看到使用qDebug()打印日志无乱码问题且关闭窗口之后析构函数自动调用
- 而且使用qDebug()打印日志可以统一关闭的(开发阶段调试使用)
三、Qt坐标
坐标体系:以左上角为原点(0,0),X向右增加,Y向下增加(对于嵌套窗口,其坐标是相对于父窗口来说的)








