Qt学习笔记
Posted 如何写出最优雅的代码
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Qt学习笔记相关的知识,希望对你有一定的参考价值。
Qt学习笔记整理,内容主要包含:
- QT的概述
- 创建QT的项目(hello qt)
- 第一个窗口及窗口属性
- 第一个按钮
- 信号与槽机制
- 带菜单栏的窗口
- 对话框
- 布局
- 常见的控件
- QT消息机制以及事件
- 绘图与绘图设备
笔记整理时间:2023年3月24日~2023年3月29日
代码仓库:https://gitee.com/wwyybtt/qt
文章目录
1. Qt概述
1.1 什么是Qt
Qt是一个跨平台的C++图形用户界面应用程序框架。它为应用程序开发者提供建立艺术级图形界面所需的所有功能。它是完全面向对象的,很容易扩展,并且允许真正的组件编程。
常见GUI:
- Qt:支持多平台开发;支持CSS;面向对象特性体现突出;发展趋势良好。
- MFC:仅在Windows开发;运行效率高;库安全性好。
1.2 Qt的发展史
- 1991年Qt最早由奇趣科技开发
- 1996年进入商业领域,它也是目前流行的linux桌面环境KDE的基础
- 2008年奇趣科技被诺基亚公司收购,Qt称为诺基亚旗下的编程语言
- 2012年Qt又被 Digia公司收购
- 2014年4月跨平台的集成开发环境Qt Creator3.1.0发布,同年5月20日配发了Qt5.3正式版,至此Qt实现了对i0S、android、WP等各平台的全面支持。当前Qt最新版本为5.13.2(2019.12之前)
1.3 支持的平台
- Windows - XP、Vista、Win7、Win8、Win2008、Win10
- Uinux/X11 - Linux、Sun Solaris、HP-UX、Compaq Tru64 UNIX、IBM AIX、SGI IRIX、FreeBSD、BSD/OS、和其他很多X11平台
- Macintosh - Mac 0s x
- Embedded -有帧缓冲支持的嵌入式Linux平台,Windows CE
1.4 优点
- 跨平台,几乎支持所有的平台
- 接口简单,容易上手,学习QT框架对学习其他框架有参考意义。
- 一定程度上简化了内存回收机制
- 开发效率高,能够快速的构建应用程序。
- 有很好的社区氛围,市场份额在缓慢上升。可以进行嵌入式开发。
1.5 成功案例
- Linux桌面环境KDE
- Skype 网络电话
- Google Earth谷歌地图
- VLC多媒体播放器
- virtualBox虚拟机软件
- 咪咕音乐
- WPS Office
- 极品飞车
1.6 Qt的下载与安装
下载地址:https://download.qt.io/archive/qt/
安装流程参考:http://t.csdn.cn/sb407
这里以5.13版本为例:
- 下载对应平台的安装包
- 我选择的是:qt-opensource-windows-x86-5.13.2.exe
- 执行安装程序
- 注册登录账户
- 选择安装路径
- 我选择的是:D:\\ProgramFiles\\Qt\\Qt5.13.2
- 组件选择
- 预计使用6G左右空间
- 安装完成
1.7 QtCreator介绍
Qt和QtCreator的区别:
- Qt:通俗来说,开发工具包
- QtCreator:集成的编译器,Qt的桌面环境
QtCreator主页面:
- 欢迎选项
- 工程:创建工程、打开工程
- 示例:demo程序,可下载运行研读代码
- 教程:一般需要翻墙才能看
- 编辑选项:
- 编辑项目文件
- 设计选项:
- 设计UI
- Debug选项:
- 调试
- 项目设置选项:
- 一般不设置
- 帮助选项:
- 帮助手册
- 可以查询
2. 创建Qt项目
创建项目的方式:
- 方式1:欢迎->Projects->New Project
- 方式2:菜单栏->文件->新建文件或项目
打开项目:
- 打开之前创建的项目
- 方式1:欢迎->Projects->Open Project
- 方式2:菜单栏->文件->打开文件或项目
创建工程时需要注意:
- 项目名称
- 一般不要有特殊符号,不要有中文
- 选择Application->Qt Widgets Application
- 例:01_demo
- 创建路径(项目保存路径)
- 路径不要带中文
- 更改为:D:\\2021code\\Qt,设置为默认的项目路径
- 创建类的基类:
- 三种基类(Base class)
- QMainWindow:带菜单栏的窗口
- QWidget:空白窗口
- QDialog:对话窗口
- 首次创建项目,我们选择:Details->Base calss->Qwidget;Source file->取消勾选Generate form(即不使用ui)
- 创建类的时候,类名首字母大写
- 编译和运行
Qt项目框架及文件介绍:
- .pro文件:工程文件,是qmake自动生成的用于生产makfile的配置文件
QT += core gui //包含的模块 greaterThan(QT_MAJOR_VERSION, 4): QT += widgets //大于Qt4版本才包含此模块 CONFIG += c++11 DEFINES += QT_DEPRECATED_WARNINGS //定义编译选项,表示有些功能被标记为过时了,编译器就会发出警告 SOURCES += \\ main.cpp \\ //源文件 widget.cpp HEADERS += \\ widget.h //头文件
- main.cpp
#include "widget.h" //Qt中一个类对应一个头文件,类名就是头文件名 #include <QApplication> //Qt系统提供的标准类名声明头文件 int main(int argc, char *argv[]) QApplication a(argc, argv); //应用程序类(整个后台管理的命脉,处理应用程序的初始化和结束,事件处理调度。注意不管有多少窗口,一个QApplication类就可) Widget w; //实例化对象,调用构造函数 w.show(); //显示图形界面 return a.exec(); //主事件循环,在exec函数中,Qt接受并处理用户和系统的事件并且将他们传递给适当的窗口控件
- widget.cpp
#include "widget.h" Widget::Widget(QWidget *parent) : QWidget(parent) Widget::~Widget()
- widget.h
#ifndef WIDGET_H #define WIDGET_H #include <QWidget> class Widget : public QWidget Q_OBJECT public: Widget(QWidget *parent = nullptr); ~Widget(); ; #endif // WIDGET_H
2.1 快捷键
代码自动对齐:Ctrl + i
快速添加/取消注释:Ctrl + /
3. 设置窗口属性
3.1 确定代码书写位置
.pro文件用于生成可执行文件,.main.cpp主函数,widget.cpp类的函数,widget.h类和头文件。
一般情况下,窗口的属性和添加控件、对控件的操作都会在类的构造函数中书写
- 优点:可以让主程序中无多余代码
3.2 帮助手册如何查看
- 方法1:帮助->定位到索引->输入要查询的内容
- 方法2:Assistant,Qt助手
帮助手册如何查看,以QWidget类为例:
- 索引->对应的类
- 类的说明:类的头文件、组件、基类、派生类
- 类的内容(查找函数的途径):公有函数(Public Funcations)、重载公有函数(Reimplemented Public Functions)、公有的槽函数(Public Slots)……
- 注意:如果有些函数在类中未找到,还可以去基类中查找。
3.3 设置窗口属性及中文乱码解决
中文乱码解决:
- 在创建项目之前,Qt主界面->工具->选项->文本编辑器->行为->默认编码,选择UTF-8,Apply->OK
设置窗口属性:在widget.cpp中
Widget::Widget(QWidget *parent) : QWidget(parent) //修改窗口的标题(第一个窗口) this->setWindowTitle("第一个窗口"); //设置窗口的大小,设置完成可以拉伸 //this->resize(800, 600); //设置窗口固定大小,设置完成不可以拉伸 this->setFixedSize(500, 500);
4. 第一个按钮
新建工程:02_demo
4.1 创建第一个按钮
使用帮助手册查找QPushButton类
- #include
创建按钮的步骤:
- 包含头文件(.cpo)及模块(.pro)
#include <QPushButton> //.cpp QT += widgets //.pro
- 调用类的构造函数创建并显示按钮
//创建按钮方式1 QPushButton* button = new QPushButton; //button->show();//会新开一个窗口显示按钮 //设置按钮的父对象为窗口,使按钮在窗口上显示 button->setParent(this);
- 设置按钮的属性
//----------设置按钮的属性 //设置按钮的文字、内容 button->setText("第一个按钮"); //设置按钮的显示位置 button->move(100, 100); //设置按钮的大小 button->setFixedSize(400, 400);
- 创建按钮的第二种方法
//创建按钮方式2 QPushButton* button2 = new QPushButton("第二个按钮", this); this->resize(600, 400);
创建按钮两种方式的区别:
- 方式1:窗口默认大小,按钮显示在左上角
- 方式2:窗口根据按钮的大小来创建,使用方法2,一般还需要调用resize函数重置窗口大小
4.2 对象树(对象模型)
在Qt中创建对象的时候会提供一个Parent对象指针,下面来解释这个parent到底是干什么的。
概念:Qt对象间父子关系。
- QObject是以对象树的形式组织起来的。
- 当你创建一个QObject对象时,会看到QObject_的构造函数接收一个QObject指针作为参数,这个参数就是parent,也就是父对象指针。这相当于,在创建QObject对象时,可以提供一个其父对象,我们创建的这个QObject对象会自动添加到其父对象的children()列表。
- 当父对象析构的时候,这个列表中的所有对象也会被析构。(注意,这里的父对象并不是继承意义上的父类! )
- QWidget是能够在屏幕上显示的一切组件的父类。
- QWidget继承自QObject,因此也继承了这种对象树关系。一个孩子自动地成为父组件的一个子组件。因此,它会显示在父组件的坐标系统中,被父组件的边界剪裁。例如,当用户关闭一个对话框的时候,应用程序将其删除,那么,我们希望属于这个对话框的按钮、图标等应该一起被删除。事实就是如此,因为这些都是对话框的子组件。
- 当然,我们也可以自己删除子对象,它们会自动从其父对象列表中删除。比如,当我们删除了一个工具栏时,其所在的主窗口会自动将该工具栏从其子对象列表中删除,并且自动调整屏幕显示。
解决问题:Qt引对象树的概念,在一定程度上解决了内存问题。
- 当一个QObject对象在堆上创建的时候,Qt会同时为其创建一个对象树。不过,对象树中对象的顺序是没有定义的。这意味着,销毁这些对象的顺序也是未定义的。
- 任何对象树中的QObject对象 delete 的时候,如果这个对象有parent,则自动将其从parent 的children()列表中删除;如果有孩子,则自动delete每一个孩子。Qt保证没有QObject会被delete 两次,这是由析构顺序决定的。
- 如果QObject在栈上创建,Qt保持同样的行为。正常情况下,这也不会发生什么问题。
4.3 对象树使用注意
C++在栈上创建对象时,是后创建的先析构,对于Qt的对象树,有以下需要注意的地方:
// 代码1 QWidget window; QPushButton button = QPushButton("退出", &window); // 代码2 QPushButton quit("Quit"); QWidget window; quit.setParent(&window);
- 代码1无问题:
- 因为栈一般先构造的后析构,代码1中执行结束后会先析构button,同时将button从window的子对象列表删除,然后析构window。因为window中已经无button子对象,仅析构window。
- 代码2有问题:
- 代码执行结束后会先析构window,而析构window时,会先析构它的子对象quit,然后析构window。接下来会再次析构quit,会导致二次析构,程序崩溃!
- 如何解决:在Qt中,尽量在构造的时候就指定parent对象,并且大胆地在堆上创建!
4.4 Qt窗口坐标体系
窗口坐标体系:
- 注意:对于嵌套窗口,其坐标是相对于父窗口来说的。
5. 信号与槽机制
5.1 信号与槽机制介绍
信号槽是Qt 框架引以为豪的机制之一。所谓信号槽,实际就是观察者模式。当某个事件发生之后,比如,按钮检测到自己被点击了一下,它就会发出一个信号(signal)。这种发出是没有目的的,类似广播。如果有对象对这个信号感兴趣,它就会使用连接(connect)函数,意思是,将想要处理的信号和自己的一个**函数(称为槽(slot))**绑定来处理这个信号。也就是说,当信号发出时,被连接的槽函数会自动被回调。
- 这就类似观察者模式:当发生了感兴趣的事件,某一个操作就会被自动触发。
- 优点:松散耦合,信号发出端和接受端可以毫无关联,如果要关联就用connect函数
5.2 connect函数与系统自带的信号和槽函数
5.2.1 connect函数常用的格式
connect()函数是QObject类中的公有函数,其声明如下:
QMetaObject::Connection connect(const QObject *sender, const char *signal, const char *method, Qt::ConnectionType type = Qt::AutoConnection) const
connect()函数最常用的一般形式:
connect(sender, signal, receiver, slot);
- sender:发出信号的对象
- signal:发送对象发出的信号
- receiver:接收信号的对象
- slot:接收对象在接收到信号之后所需要调用的函数(槽函数)
5.2.2 系统自带的信号与槽函数
大部分的类都存在槽函数和信号,打开帮助手册,以QWidget为例:
- Public Slots:公有的槽函数
- Signals:信号,有时类中没有Signals时,可以查找其基类
5.2.3 实例
要求:创建一个按钮,点击按钮能实现关闭窗口的功能。
新建项目:03_demo
//创建按钮 QPushButton* button = new QPushButton("点击关闭窗口", this); //重置窗口大小 this->resize(600, 400); //信号与槽函数 connect(button, &QPushButton::clicked, this, &Widget::close);
5.3 自定义信号和槽
5.3.1 无参的信号与槽
所在项目:03_demo
步骤1:确定场景
- 老师饿了,学生请客(以这个为例)
- 小哥敲门,家人开门(自己练习实现)
步骤2:添加老师类和学生类
步骤3:
- 在老师类中声明信号(声明即可)
- teacher.h
- 信号一般在类声明中的signals下写
- 信号返回值为void,参数可以添加或为空
- 仅声明,不实现
- 一般情况下可以重载
- 并且在学生类中声明槽函数(声明并实现)
- 在student.h声明
- 槽函数声明一般在类声明中的public slots下写(对于高版本的Qt,也可以写到public或者全局)
- 槽函数返回值为void,参数可以添加或者为空
- 槽函数声明且实现
- 槽函数定义实现在student.cpp中
//teacher.h signals: void hungury(); //student.h public slots: void treat(); //student.cpp void Student::treat() qDebug() << "请吃饭"; //包含头文件:#include <QDebug>
步骤4:创建老师对象和学生对象,并使用connect连接
- 在widget.cpp中创建并连接
this->tea = new Teacher(this); this->stu = new Student(this); connect(tea, &Teacher::hungury, stu, &Student::treat);
步骤5:触发信号
- widget.h中声明触发信号的成员函数
- widget.cpp定义触发信号的成员函数
- 调用该函数
//widget.h public: void ClassOver(); //widget.cpp void Widget::ClassOver() emit tea->hungury(); //调用 ClassOver();
补充:
- 点击按钮,请老师吃饭
connect(button, &QPushButton::clicked, this, &Widget::ClassOver); this->tea = new Teacher(this); this->stu = new Student(this); connect(tea, &Teacher::hungury, stu, &Student::treat);
- 信号连接信号
this->tea = new Teacher(this); this->stu = new Student(this); connect(tea, &Teacher::hungury, stu, &Student::treat); connect(button, &QPushButton::clicked, tea, &Teacher::hungury);
5.3.2 重载自定义信号与槽(有参)
步骤1:重新写信号(带参数)
void hungury(QString food);
步骤2:重新写槽函数声明及定义(带参数)
void treat(QString food); void Student::treat(QString food) qDebug() << "请老师吃饭:" << food;
步骤3:由于函数重载了,所以需要利用函数指针指向函数地址,然后再作连接
//定义函数指针 void (Teacher::*teachersignal)(QString) = &Teacher::hungury; void (Student::*studentslot)(QString) = &Student::treat; connect(tea, teachersignal, stu, studentslot); ClassOver();
5.4 信号与槽总结
自定义信号与槽注意事项:
- 发送者与接受者需要是QObject的子类(槽函数全局,lambda除外)。
- 信号与槽函数返回值都是void。
- 信号需要声明,不需要定义实现。槽函数需要声明也需要定义实现。
- 槽函数是普通的成员函数,作为成员函数,会受到public、private、protected的影响。
- 使用emit在恰当的位置发送信号。
- 使用connect()函数连接信号和槽。
- 任何成员函数、static函数、全局函数和Lambda 表达式都可以作为槽函数。
- 信号槽要求信号和槽的参数一致,所谓一致,是参数类型一致。
- 如果信号和槽的参数不一致,允许的情况是,槽函数的参数可以比信号的少,即便如此,槽函数存在的那些参数的顺序也必须和信号的前面几个一致起来。这是因为,你可以在槽函数中选择忽略信号传来的数据(也就是槽函数的参数比信号的少)。
5.5 信号槽的扩展
- 一个信号可以和多个槽相连
- 如果是这种情况,这些槽会一个接一个的被调用,但是它们的调用顺序是不确定的。
- 多个信号可以连接到一个槽
- 只要任意一个信号发出,这个槽就会被调用。
- 一个信号可以连接到另外的一个信号
- 当第一个信号发出时,第二个信号被发出。除此之外,这种信号-信号的形式和信号-槽的形式没有什么区别。
- 槽可以被断开连接,使用disconnect。
- 槽也可以被取消连接(当一个对象delete了,就会取消这个对象上的槽)
- 使用C++11中的lambda表达式
6. Lambda表达式
6.1 Lambda表达式的介绍
概念:C++11中的Lambda表达式用于定义并创建匿名的函数对象,以简化编程工作。
语法:
[capture](parameters) mutable ->return-typestatement; 1、[capture]捕获列表,捕获的是那些到定义Lambda为止时Lambda所在作用范围内可见的局部变量 2、(parameters)参数列表,与普通函数的参数列表一致的。 3、mutable可修改标示符,按值传递捕获列表参数时(默认仅读权限),加上mutable修饰符后,可以修改按值传递进来的拷贝 4、->return-type返回值类型 5、statement函数体,内容跟普通函数一致 6、分号不能省略
注意:
- [] 标识一个Lambda的开始,这部分必须存在,不能省略。
- () 参数列表,如果不需要传递参数的话,()可以一同省略。
- 如果使用mutable,参数列表 () 不能省略的即使参数为空;如果使用mutable,修改拷贝,而不是值本身。
- 返回值类型,如果不需要,->return-type都可省略。
- 函数体,可以使用参数列表,也可以使用捕获列表。
补充:对于捕获列表[]的参数形式,有如下情况
- 空。没有使用任何函数对象参数。
- =。函数体内可以使用Lambda所在作用范围内所有可见的局部变量(包括Lambda 所在类的this),并且是值传递方式(相当于编译器自动为我们按值传递了所有局部变量)。
- &。函数体内可以使用Lambda所在作用范围内所有可见的局部变量(包括Lambda所在类的 this),并且是引用传递方式(相当于编译器自动为我们按引用传递了所有局部变量)。
- this。函数体内可以使用Lambda所在类中的成员变量。
- a。将a按值进行传递。按值进行传递时,函数体内不能修改传递进来的的拷贝,因为默认情况下函数是const_的。要修改传递进来的a的拷贝,可以添加mutable修饰符。
- &a。将a 按引用进行传递。
- a,&b。将a按值进行传递,b按引用进行传递。
- =,&a,&b。除a和b 按引用进行传递外,其他参数都按值进行传递。
- &,a, b。除 a和b按值进行传递外,其他参数都按引用进行传递。
6.2 Lambda表达式的使用
一个简单的lambda表达式:
auto fun = []() qDebug() << "Lambda is running!"; ; fun(); // [] qDebug() << "Lambda is running!"; ();
一个带参数和返回值的lambda:
auto fun = [](int a, int b) qDebug() << "Lambda is running!"; return a + b; ; int sum = fun(100, 200); qDebug() << sum;
对于mutable:如果缺少mutable,下列代码将报错;省略()也会报错
int m = 10; auto fun = [m]()mutable qDebug() << "Lambda is running!"; m = 300; ;
结合信号与槽:槽函数可以使用lambda,但后面的分号要省略
QPushButton* myBtn = new QPushButton("点击", this); this->resize(600, 400); connect(myBtn, &QPushButton::clicked, this, [=]()qDebug() << "按钮被按下";); //这里的lambda省略了;
7. 带菜单栏的窗口
7.1 QMainWindow概述
QMainWindow是一个为用户提供主窗口程序的类,包含一个菜单栏(menu bar)、多个工具栏(tool bars)、多个铆接部件(dock widgets)、一个状态栏(status bary及一个中心部件(central widget)。
以Qt界面为例,说明各个部件:
- 铆接部件:浮动窗口
- 中心部件:写代码的地方
7.2 QMainWindow菜单栏
创建新的项目:04_demo
菜单栏类:QMenuBar
菜单类:QMenu
QAction类:充当子菜单(菜单项)
菜单栏创建方法:
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) resize(800, 600); //创建菜单栏 QMenuBar* menubar = new QMenuBar(this);//使用new创建 //QMenuBar* menubar = menuBar();//使用成员函数创建 this->setMenuBar(menubar); //创建菜单 QMenu* menu1 = new QMenu("文件"); QMenu* menu2 = new QMenu("编辑"); QMenu* menu3 = new QMenu("构建"); //添加菜单到菜单栏 menubar->addMenu(menu1); menubar->addMenu(menu2); menubar->addMenu(menu3); //创建菜单项/子菜单 QAction* act1 = new QAction("打开文件"); QAction* act2 = new QAction("另存为"); QAction* act3 = 事件(event)
一般来说,使用Qt编程时,我们并不会把主要精力放在事件上,因为在Qt中,需要我们关心的事件总会发出一个信号。比如,我们关心的是QPushButton的鼠标点击,但我们不需要关心这个鼠标点击事件,而是关心它的clicked()信号。这与其他的一些框架不同:在MFC中,你所要关心的是鼠标左键按下这个事件。总的来说,如果我们使用组件,我们关心的是信号槽;如果我们自定义组件,我们关心的是事件。因为我们可以通过事件来改变组件的默认操作。比如,如果我们要自定义一个QPushButton,那么我们就需要重写它的鼠标点击事件和键盘处理事件,并且在恰当的时候发出clicked()信号。
1 #include "mainwindow.h" 2 #include <QtGui/QApplication> 3 4 #include <QMouseEvent> 5 6 class EventLabel:public QLabel 7 { 8 protected: 9 void mouseMoveEvent(QMouseEvent *ev); 10 void mousePressEvent(QMouseEvent *ev); 11 void mouseReleaseEvent(QMouseEvent *ev); 12 13 }; 14 15 void EventLabel::mouseMoveEvent(QMouseEvent *ev) 16 { 17 this->setText(QString("<center><h1>Move: (%1, %2)</h1></center>") 18 .arg(QString::number(ev->x()), QString::number(ev->y()))); 19 } 20 21 void EventLabel::mousePressEvent(QMouseEvent *ev) 22 { 23 QString text; 24 text.sprintf("<center><h1>Move: (%d, %d)</h1></center>",ev->x(),ev->y()); 25 this->setText(text); 26 } 27 28 void EventLabel::mouseReleaseEvent(QMouseEvent *ev) 29 { 30 this->setText(QString("<center><h1>Move: (%1, %2)</h1></center>").arg(QString::number(ev->x()),QString::number(ev->y()))); 31 32 } 33 34 35 int main(int argc, char *argv[]) 36 { 37 QApplication a(argc, argv); 38 /*MainWindow w; 39 w.show();*/ 40 EventLabel *eventLabel=new EventLabel; 41 eventLabel->setWindowTitle("my title"); 42 eventLabel->resize(300,200); 43 eventLabel->show(); 44 return a.exec(); 45 }运行结果:
忽略事件:判断点击的按钮是左键还是右键,忽略事件调用父类的响应函数即可。
1 void EventLabel::mousePressEvent(QMouseEvent *ev) 2 { 3 if(ev->button() == Qt::RightButton) 4 { 5 QString text; 6 text.sprintf("<center><h1>Move: (%d, %d)</h1></center>",ev->x(),ev->y()); 7 this->setText(text); 8 } else 9 { 10 QLabel::mousePressEvent(ev); 11 } 12 13 14 }退出窗口选择:
1 void MainWindow::closeEvent(QCloseEvent * event) 2 { 3 if(continueToClose()) 4 { 5 event->accept(); 6 } else { 7 event->ignore(); 8 } 9 } 10 11 bool MainWindow::continueToClose() 12 { 13 if(QMessageBox::question(this, 14 tr("Quit"), 15 tr("Are you sure to quit this application?"), 16 QMessageBox::Yes | QMessageBox::No, 17 QMessageBox::No) 18 == QMessageBox::Yes) { 19 return true; 20 } else { 21 return false; 22 } 23 }
以上是关于Qt学习笔记的主要内容,如果未能解决你的问题,请参考以下文章