目录
目录
4.5 Date Edit & Time Edit (日期&时间微调框)
4.1 演示一: 绑定了信号与槽的控件 又重写了相同的事件函数
2.6 moveToThread函数 && 迁移子类对象到线程中
2.7 QThreadPool && QRunnable (线程池)
1.1 QNetworkAccessManager Class 网络数据管理器
1.2 QNetworkReply Class 获取请求响应
1.2 QNetworkRequestClass 发起请求类型
第一章 QT概述
1. QT概述
简单来说,QT就是一个跨平台的客户端技术,HTML画网页一样,而QT就是画客户端的,它不仅可以绘制界面而且可以做单机应用开发,还可以做网络程序的客户端界面开发
更专业的说法是:Qt 是⼀个 跨平台的 C++ 图形⽤⼾界⾯应⽤程序框架 ,它拥有完备的C++ 图形库和集成了一系列代码模块简化难度,开发者可以通过简单的拖拽和组合来实现复杂的应⽤程序,而且Qt 支持C++,Python,QML,Javascript 等多种语言,适合多种技术、开发方式。同时Qt 也拥有一套完整的设计、开发工具以及丰富的文档和例程,其开源社区非常活跃,这些能明显降低开发难度和缩短开发时间
其实除了QT是客户端技术,还有VB,C++、win32API 的 MFC ,Winform ,C# .net framework,Java swing/javaFx ,C++ duilib,Objective-c/swift cocoa等等
但他们不是做出来的客户端界面丑,无法跨平台,没有活跃的社区,就是没有维护了,所以一般公司做客户端界面都是用的QT;
而Qt是很多客户端跨平台的首选,因为开源、UI 库和各种功能的类库非常丰富,但这里只推荐做pc的客户端,移动端的客户端界面还是不推荐使用Qt
2. QT框架
一句话:One framework. One codebase. Any platform,这是Qt 官网的一句话,很好的概括了什么是Qt,
口号说明:
- One framework(一个框架):只需要使用一个开发框架
- One codebase(一套代码):只需要维护一份代码,而不是针对不同平台写不同的代码
- Any platform(任何平台):这套代码可以运行在多个平台上,比如 Windows、macOS、Linux,甚至移动设备(iOS/Android)或 Web 端
3. QT发展(了解)
-
Qt最早是1991年由挪威的Eirik Chambe-Eng和Haavard Nord开发的,1994年3月4日成立奇趣科技公司(Trolltech);
-
2000年奇趣科技公司为开源社区发布了遵循 GPL(GNU General Public License)许可证的开源版本;
-
2008年诺基亚公司收购了奇趣科技公司,并增加了 LGPL(GNU Lesser General Public License)的授权模式;
-
2011年3月Qt 商业授权业务出售给了芬兰IT服务公司Digia
- 2013年7月3日发布Qt5,2020年6月12日**Qt5.15** LTS 正式发布,目前已经推出Qt6。
经过多年的发展,市面上也有很多基于Qt开发的应用程序:WPS、YY语音、豆瓣电台、虾米音乐、淘宝助理、千牛、暴雪的战网客户端、VirtualBox、Google地图、Photoshop等等
4. QT优点
-
跨平台 ,⼏乎⽀持所有的平台;
-
接⼝简单,容易上⼿ ,学习 QT 框架对学习其他框架有参考意义。
-
⼀定程度上简化了内存回收机制;
-
开发效率⾼,能够快速的构建应⽤程序。
-
有很好的社区氛围,市场份额在缓慢上升。
-
可以进⾏嵌⼊式开发。
5. Qt 的应⽤场景
-
主要: 桌⾯应⽤程序, 嵌⼊式系统
-
次要: 移动应⽤程序
额外多说一下:嵌入式系统指的是日常使用的: 冰箱,洗衣机,路由器,投影仪...之类的,这些设备里面就使用了嵌入式系统
第二章 QT Creator
1. 下载
虽然目前最新的QT版本是QT6,但是我这里使用的是QT5
- 我使用的是5.12.11版的qt,大家可以根据自己的实际情况进行下载,
- 值得多说一句的是:类似于这些软件/工具的下载,不要最新的,也不要太老的
- 最新的版本出了问题不好解决,太老的版本可能又会有兼容性问题
2. 安装
说明一下:
- 可能第一个界面是需要注册账号的,直接使用邮箱+密码注册,然后会给你的邮箱中发个邮件,最后你在你的邮箱中确认那个邮件就行了(如果在安装之间断网,那么就不需要注册账号)
- 下一步,下一步,然后选择那四个组件就行了,不要全选
3. 配置环境变量
为了让操作系统/QT Creator工具,能够找到Qt SDK中提供的exe,也就是运行Qt 程序的时候,能够找到对应.dll 动态库,所以我们这里可以先把环境变量配置好
4. 基本介绍
刚才其实我们就已经把QT开发环境下好了,可能桌面没有显示,按一下win键,如下图
5. 新建项目
- 其实一路next下去也行
6. 项目结构
6.1 qt_tmp.pro
qt_tmp.pro是项目的配置文件
说明一下:
- 这里该怎么理解QT += 模块呢,其实就是我们在做大型项目是需要引入的第三方库,
- 后面我们会需要引入multimedia 模块,而这个模块是跟音视频有关的模块,简单理解为第三方库也行
6.2 mainwindow.h
说明一下:
- Q_OBJECT其实是个宏,其中有QT中的信号与槽的宏定义,更详细的后面再说
关于QT中对头文件的设定:
- 在使用Qt中内置的类时,一般来说包含头文件的名字就是和类名一致的,但是也不是所有的Qt类都需要显示包含头文件
- 毕竟在c++中,一些头文件都是间接被包含的
关于构造函数中需要穿父类的指针说明:
- QT中为了统一析构父类和子类,引入了对象树的概念(后面详细介绍)
- 而创建对象时就需要把这个对象往树上挂,而往树上挂就必须要指明父节点,
- 则构造的时候就需要传一个父类的指针,
6.3 mainwindow.cpp
6.4 main.cpp
说明一下:
- 是先有应用,再有窗口,窗口是放在应用里的,可能有多个窗口,所以他们2个对象实例化的顺序不能颠倒
这里简单说一下a.exec(),其实可以简单的理解为一个死循环,但还做了一些其他的东西,
- 如果没有最后一行代码,而是写成了return 0; 那么这个窗口会闪一下,就退出了,
- 简单解释:能出现窗口是因为有w.show();// 显示窗口,而w对象又是栈上开辟的,出了作用域就没了
顺便再说一下:w.show()
- .show()方法表示让控件显示处理
- .hide()方法表示让控件隐藏
- widget的父类时QWidget,则上面的那两个方法都是QWidget提供的
6.5 mainwindow.ui
上面那种格式是xml格式,而这里的xml格式就是去描述这个界面是怎么样的,进一步的qmake会调用相关的工具,依据这个xml文件生成一些c++代码,从而把完整的界面构建出来
6.6 临时文件
说明一下:
- 在运行一次程序之后,就会在 项目并列的地方,多出一个"build-xxxx"目录
- 这个目录里面就是该项目运行过程中,生成的一些临时文件
7. 第一个程序hello world
7.1 纯图形化方式
说明一下:
- 以图形化方式实现界面,主要是在qt designer 中拖动控件,调调属性等
- 而我刚才往界面上拖拽了一个QLabel控件,此时,ui文件的xml中就会多出来这一段代码
- 进一步的qmake就会在编译项目的时候,基于这个内容,生成一段C++代码,通过这个C++代码构建出界面内容
7.2 纯代码方式
方式一:重建一个cpp文件
#include <iostream>
#include <QMainWindow>
#include <QLabel>
#include <QApplication>
using namespace std;
int main(int argc, char *argv[])
{
// cout << "Hello World!" << endl;
QApplication a(argc, argv);
// 因为show这个方法本来就是QMainWindow类的,所以直接实例化这个类的对象就行
QMainWindow w;
w.setGeometry(0,0,800,600);
w.setWindowTitle("第一个程序");
QLabel* label = new QLabel(&w);
// 对这个控件做相关属性设置
label->setText("hello world");
label->setGeometry(160,110,400,71);
label->setStyleSheet("font: 75 20pt Consolas;");
w.show();
return a.exec();
}
方式二:重建一个qt设计类文件
说明一下:
- 一般通过代码来构造界面的时候,通常会把构造界面放到Widget/MainWindow的构造函数中
- 这里的this指针,是给当前这个label对象,指定一个父对象(以后会说的对象树)
- 在QT中的字符串和C++/C中的字符串是不一样的,当时C++/C的字符串不好用,所以QT为了字节的开发能变的顺畅,就自己发明了一套轮子,搞了一系列基础类,来支持QT的开发,包括但不限于
字符串->QString 动态数组->QVector 链表->QList 字典->QMap - 在QString中也提供了 C风格字符串作为参数的构造函数,不显示构造QString,上述代码中,C风格字符串也会隐式构造成QString对象
- 每个标签都有一个同名的头文件,但有时候也会被其他头文件间接包含,
比如: QString 对应的头文件,已经被很多Qt内置的其他类给间接包含了,因此一般不需要显示包含QString头文件
这里还有一个值得思考的点: 一个对象new出来了,但是却没有delete,这难道不会造成内存泄漏吗
- 在上述代码中,Qt不会产生内存泄漏,label对象会在合适的时候被析构函数释放~~
- 之所以能够把对象释放掉,主要是因为把这个对象挂到了对象树上了,交给Qt的对象树统一管理
- 但如果这个对象是在栈上创建的话,就有可能会存在一些"提前释放"的问题,此时就会导致对应的控件就在界面上不存在了
- 推荐: 在堆上创建对象,并挂在对象树上
7.3 验证对象树会统一析构
- 注: 上面少写了个delete
- 自己实现label类,并继承QLabel,然后再自主实现析构函数
- 则可以在输出日志中观察到 自己实现的析构函数被调用了,则对象树的确会统一析构
- 这里使用qDebug进行输出日志(不建议使用cout),还有一个好处->可以进行统一关闭
输出日志一般是在开发阶段,调试程序的时候使用
8. 快捷键
- 注释:ctrl + /
- 运⾏:ctrl + R
- 编译:ctrl + B
- 字体缩放:ctrl + ⿏标滑轮
- 查找:ctrl + F
- 整⾏移动:ctrl + shift + ⬆/⬇
- 帮助⽂档:F1
- ⾃动对⻬:ctrl + i;
- 同名之间的 .h 和 .cpp 的切换:F4
- ⽣成函数声明的对应定义: alt + enter
9. 使⽤帮助⽂档
-
方法一:光标放到要查询的类名/⽅法名上, 直接按 F1(推荐)
-
方法二:Qt Creator 左侧边栏中直接⽤⿏标单击 "帮助" 按钮
-
方法三:找到 Qt Creator 的安装路径,在 "bin" ⽂件夹下找到 assistant.exe,双击打开;
10. Qt 中的命名规范
-
类名:⾸字⺟⼤写,单词和单词之间⾸字⺟⼤写;MyClass MyAdd
-
函数名及变量名:⾸字⺟⼩写,单词和单词之间⾸字⺟⼤写;studentCount
11. Qt 窗⼝坐标体系
计算机中的坐标系和数学中的坐标系是不一样的,y轴是向下增长的
说明一下:
- 对于嵌套窗口,其坐标是相对于父窗口来说的
第三章 信号与槽
1. 基本概念
信号:信号就是一个通知机制,比如在某个状态做了一个什么事情,或者状态发生了改变,需要有别人来处理的时候,就要发出一个信号,就相当于发出一个通知,信号是没有实现的,信号的成员函数是不需要被定义的
槽函数:信号发出去以后,一定要做一件事情,而这个事情就是由槽函数来做的,简单来说就是对信号做出的一种响应
在QT中信号和槽函数一定要先进行绑定才可以的,且一定是先关联信号再进行绑定(顺序不能颠倒),后续只要信号触发了,Qt就会自动执行槽函数
信号与槽这种机制是QT框架很重要的机制,它的优点有:松散耦合,但缺点:效率较低
2. 图形化操作
比如我现在要在窗口中放置一个按钮,当我点击这个按钮的时候,这个窗口就会关闭
注意:在ui界面中改了控件名字,是需要重新构建的,否则控件名将不会生效
2.1 编写槽函数
在ui界面中选中控件,然后右键转到槽,然后选择对应的信号
之后在头文件对应的类中就会自动生成一个信号,并把光标跳转到自动生成的槽函数中
void MainWindow::on_pushButton_clicked()
{
// 关闭窗口
this->close();
}
说明一下:
- 通过图形化界面的方式定义槽函数,的确很方便,很多步骤它都帮我们做完了
- 但在真实的开发环境中我们需要自定义信号,自定槽函数,自己去关联信号与槽函数,
3. 纯代码操作
3.1 connect函数
connect(const QObject * sender,const char* signal,const QObject* receiver
const char* method, Qt::ConnectionType type = Qt::AutoConnection)
参数说明:
- sender: 描述当前信号是那个控件发出的
- signal: 描述信号类型
- receiver: 描述了那个控件负责处理
- method: 描述了这个控件怎么处理(要处理信号的对象提供成员函数)
3.2 深度理解connect函数参数
- 我们上面传信号和槽都是传递的函数指针,而在C++中是不允许使用2个不同指针类型,相互传参相互赋值的(函数传参,本质就是赋值)
- 其实这个函数声明是以前旧版本的QT的connect函数声明,以前个信号参数传参,要搭配要给SIGNAL宏,给槽函数传参要搭配一个SLOT宏(这样做的目的是 将 传入参数 转成char*)
- connect(button,SIGNAL(&QPuhsButton::clicked),this,SLOT(&Widget::close))
但是后来因为书写起来太麻烦了,在Qt5时进行了改进
- 使用模板实现泛型编程,重载构造函数
- 此时connect函数就有一定的参数检查功能:参数1和参数2不匹配 or 参数3和参数4不匹配,
都会编译出错 - 则要求参数2必须是参数1的成员函数,参数4必须是参数3的成员函数
4. 查看信号与槽
自己平时的积累 + 查看文档
- QPushButton自己本身没有clicked信号,但是它继承了QAbstractButton
- 在QAbstractButton中就有一个clicked点击信号
- 注: 这个QAbstractButton又使继承QWidget
- 我们写的widget在定义的时候就是继承QWidget的,而QWidget就有close这个关闭槽
- 注: QWidget这个类又是继承自QObject类的
5. 自定义槽
- 在Qt中,除了通过connect来连接信号槽之外,还可以通过函数名字的方式自动连接
5. 自定义信号
- 自定义信号,本质就是一个函数,只需要声明就行了,而这个函数的定义,是Qt在编译过程中,自动生成的(我们无法干预)
- 而作为信号参数,这个函数的返回值,必须是void的,有没有参数都是可以的,甚至可以支持重载
- Qt内置的信号,不需要手动触发,而自定义信号,需要手动代码触发 emit mySignal();
- 这里使用图形化方式创建一个按钮,在通过这个按钮发送 自定义信号
-
发送信号的操作, 也可以在任意合适的代码中. 不一定非得在构造函数里
6. 带参数的信号与槽
-
带有参数的信号,要求信号的参数和槽的参数要一致
-
类型,个数要满足要求(信号的参数个数要多于槽的参数个数)
7. 额外说明
7.1 前置条件
- Qt 中如果要让某个类能够使用信号槽(可以在类中定义信号和槽函数),则必须要在类最开始的地方,写下Q_OBJECT宏
- 将这个宏展开,会得到一大段代码,然后而这一大段代码又可以展开
7.2 设计目的
- 解耦,: 把触发 用户操作的控件 和 处理对应用户的操作逻辑 解耦合
- 实现"多对多"效果
一个信号,可以connect到多个槽函数上
一个槽函数也可以被多个信号connect - 但是多对多的需求实际中并不常见,所以以后图形化开发框架都没有支持多对多
7.3 disconnect断开连接
主动断开往往是把信号重新绑定到另一个槽函数上
-
如果没有 disconnect, 就会构成 一个信号绑定了两个槽函数. 触发信号的时候, 两个槽函数都会执行
7.4 lambda表达式
定义槽函数的时候,也是可以使用lambda表达式的,
- 为了解决lambda访问作用域的问题,就需要引入变量捕捉的语法规则
- lambda语法是c++11中引入的,对于Qt5及其更高版本,默认就是按照c++ 11来编译的
- 如果使用Qt4或者更老的版本,就需要手动在.pro文件中加上C++的编译选项:CONFIG += c++11
8. 登录案例(动手)
我们要实现的是一个登录和注册的页面,具体参考下图:
要求说明:
- 实现一个登录系统的主页面,2个功能,一个登录,一个注册
- 登录成功,需要跳转到欢迎界面,并欢迎这个登录的用户
- 注册成功,需要跳转到主页面,并把注册到的信息填入对于的输入框中
- .....
注意: 信号与槽不能嵌套,且电脑分辨率不同,会对qt designer产生影响
4.1 关键点:如何新建一个窗口
说明一下:
- 然后选择对应的控件就行了
4.2 关键点:如何展示子类的页面
推荐:在父类中添加一个成员变量,类型是子类的类型,然后在构造函数中实例化这个对象,如下:
而想要展示一个页面直接调用show方法,关闭一个页面直接调用hide方法
4.3 关键点:如何把父类的数据传递给子类
可以使用信号与槽,但这里推荐直接传递参数就可以了,处理参数的函数在子类中实现就可以了, 毕竟父类中有个成员变量是指向子类的(具体情况如上)
4.4 关键点:如何把子类的数据传递给父类
推荐:使用自定义信号与槽,首先在子类中自定义一个信号,然后再父类的构造函数中connect自定义信号与槽,然后再子类中要传数据的地方emit自定义信号,对了槽函数需要在父类中实现
第四章 Qt Core
Qt Core也就是Qt的核心部分,围绕官方介绍可以理解为以下这些特性:a. 元对象系统 b.属性系统 c.对象模型 d.对象树 e.信号槽
1. QString常见函数
1.1 append - 字符串拼接
void function1()
{
QString str = "123456";
str.append("abc");// 字符串拼接
qDebug() << str;
}
1.2 prepend - 头前拼接
void function2()
{
QString str = "123456";
str.prepend("abc");// 字符串拼接
qDebug() << str;
}
1.3 arg - 格式化
void function3()
{
QString str = "姓名:%1,年龄:%2,性别:%3";
QString format = str.arg("张三").arg(30).arg("男");
qDebug() << format;
}
1.4 contains - 判断是否包含
void function4()
{
QString str = "123abc456";
if(str.contains("abc"))
{
qDebug() << "包含abc" << endl;
}
else{
qDebug() << "不包含abc" << endl;
}
}
1.5 isEmpty && isNull
void function5()
{
QString str1 = "";
QString str2;
if(str1.isEmpty()){
qDebug() << "str1是empty的" << endl;
}
if(str1.isNull()){
qDebug() << "str1是null的" << endl;
}
if(str2.isEmpty()){
qDebug() << "str2是empty的" << endl;
}
if(str2.isNull()){
qDebug() << "str2是null的" << endl;
}
}
说明一下:
- isEmpty()是用来判断字符串是否是空字符串的
- isNull()既能判断是否是NULL,而且还能判断是不是空字符串
1.6 startWith -- 判断是否以xxxxx打头
void function6()
{
QString str = "123456";
if(str.startsWith("123"))
{
qDebug() << "str是以12打头的" << endl;
}
}
1.7 indexOf - 找xxx出现的位置
void function7()
{
QString str1 = "www.baidu.com";
//找第一次.出现的位置
qDebug() << str1.indexOf(".") << endl;
}
说明一下:
- 这个函数就类似于c++中的find函数
- 同理下标索引也是从0开始的
1.8 repalce - 替换
QString &QString::replace(int position, int n, const QString &after)
参数说明:
- 第一个参数是被替换字符串的位置
- 第二个参数是被替换字符串的长度
- 第三个参数是新的字符串
void function8()
{
QString str1 = "www.baidu.com";
//找第一次.出现的位置
QString target = ".";
int index = str1.indexOf(target);
str1.replace(index,target.length(),"//");
qDebug() << str1 << endl;
}
1.9 split - 分割字符串
void function9()
{
QString str = "111:222:333:444";
// 将str按照:分割成4个子字符串
QStringList list = str.split(":");
qDebug() << list << endl;
}
说明一下:
- split的返回值是QStringList类型,而这个类型就相当于一个只能存放QString的vector
1.10 string 与 QString相互转换
- QString::fromStdString(s);// 把std::string 转换成QString
- s.toStdString();//把QString转换成std::string
2. QByteArray
多用于处理字节数组
void function1()
{
QString str = "123456";
QByteArray data = str.toUtf8();
QString str1 = QString(data);
qDebug() << str1 << endl;
}
3. QStringList && QVector
QStringLis等价于QVector<QString>
void function2()
{
QStringList v1 = {"ab"};
v1.push_back("cd");
for(auto e : v1){
qDebug() << e << " ";
}
QVector<QString> v2 = {"ab"};
v2.push_back("cd");
for(auto e : v2){
qDebug() << e << " ";
}
}
说明一下:
- 除了QVector还有QList,QStack等等,前面已经说了QT它又搞了一套新的轮子,
- 其实就是又封装了一遍
第五章 常用控件
1. QWidget 核心属性
1.1 objectName
1.2 enabled
API | 说明 |
isEnabled()
|
获取到控件的可⽤状态
|
setEnabled
|
设置控件是否可使⽤.
true
表⽰可⽤,
false
表⽰禁⽤
|
1.3 geometry && window frame
geometry: x y width height
API
|
说明
|
geometry()
|
获取到控件的位置和尺⼨. 返回结果是⼀个
QRect,
包含了 x, y, width, height. 其
中 x, y 是左上⻆的坐标
|
setGeometry
(QRect)
setGeometry
(int x, int y,
int width, int height)
|
设置控件的位置和尺⼨. 可以直接设置⼀个
QRect
, 也可以分四个属性单独设置.
|
- 注意: setGeometry(QRect),使用这个函数可能会改变原来的控件的width 和 height
API
|
说明(以下API: 计算时包含 window frame)
|
x()
|
获取横坐标
|
y()
| 获取纵坐标 |
pos()
|
返回 QPoint 对象, ⾥⾯包含 x(), y(), setX(), setY() 等⽅法.
|
frameSize()
|
返回 QSize 对象, ⾥⾯包含 width(), height(), setWidth(), setHeight() 等⽅法.
|
frameGeometry()
|
返回 QRect 对象. QRect 相当于 QPoint 和 QSize 的结合体. 可以获取 x, y, width, size.
|
API
| 说明(以下API: 计算时不包含 window frame) |
width() |
获取宽度
|
height()
|
获取⾼度
|
size()
|
返回 QSize 对象, ⾥⾯包含 width(), height(), setWidth(), setHeight() 等⽅法.
|
rect()
|
返回 QRect 对象. QRect 相当于 QPoint 和 QSize 的结合体. 可以获取并设置 x,
y, width, size.
|
geometry()
|
返回 QRect 对象. QRect 相当于 QPoint 和 QSize 的结合体. 可以获取 x, y,
width, size
|
setGeometry()
|
直接设置窗⼝的位置和尺⼨. 可以设置 x, y, width, height, 或者 QRect 对象
|
1.4 windowTitie
API | 说明 |
windowTitle()
|
获取到控件的窗⼝标题.
|
setWindowTitle(const QString& title)
|
设置控件的窗⼝标题
|
1.5 windowlcon
API
|
说明
|
windowIcon()
|
获取到控件的窗⼝图标. 返回 QIcon 对象
|
setWindowIcon(const QIcon& icon
)
|
设置控件的窗⼝图标
|
通过 qrc 管理图⽚作为图标
给Qt项目引入一个额外的xml文件(后缀名使用.qrc表示),在这个xml中把要使用的图片资源给导入进来,并且在xml中记录
Qt在编译项目的时候,就会根据qrc中描述的图片信息,找到图片内容,并且提取出图片的二进制数据,把这些二进制数据转换成C++代码,最终编译到exe里
新建qrc文件
-
注意 : 添加的⽂件必须是在 qrc ⽂件的同级⽬录, 或者同级⽬录的⼦⽬录中.
- 在引入了qrc机制之后,就可以直接用相对路径访问的这个图片
- qrc机制主要解决2个问题:
问题一:确保图片所在的路径在目标用户机器上存在
问题二:确保图片不会被用户搞没了
1.6 windowOpacity
API | 说明 |
windowOpacity()
|
获取到控件的不透明数值. 返回
float
, 取值为 0.0 -> 1.0 其中 0.0 表⽰全透明,1.0 表⽰完全不透明 |
setWindowOpacity(float n)
|
设置控件的不透明数值.
|
- 注意: 窗口的不透明度,变化并非是精确的,这主要和浮点数在内存中的存储有关
1.7 cursor
API
| 说明 |
cursor()
|
获取到当前 widget 的 cursor 属性, 返回 QCursor 对象.
当⿏标悬停在该 widget 上时, 就会显⽰出对应的形状
|
setCursor(const QCursor& cursor)
|
设置该 widget 光标的形状. 仅在⿏标停留在该 widget 上时⽣效.
|
QGuiApplication::setOverrideCursor(co
nst QCursor&
cursor
)
|
设置全局光标的形状. 对整个程序中的所有 widget 都会⽣效. 覆盖
上⾯的 setCursor 设置的内容.
|
- QPixmap是一个位图对象
1.8 font
API
|
说明
|
font()
|
获取当前 widget 的字体信息. 返回 QFont 对象
|
setFont(const QFont& font)
|
设置当前 widget 的字体信息
|
关于 QFont
属性
|
说明
|
family
|
字体家族. ⽐如 "楷体", "宋体", "微软雅⿊" 等.
|
pointSize
|
字体⼤⼩
|
weight
|
字体粗细. 以数值⽅式表⽰粗细程度取值范围为 [0, 99], 数值越⼤, 越
粗
|
bold
|
是否加粗. 设置为 true, 相当于 weight 为
75
. 设置为 false 相当于 weight 为
50
|
italic
|
是否倾斜
|
underline
|
是否带有下划线
|
strikeOut
|
是否带有删除线
|
- 也可以使用ui界面直接修改
1.9 tooltip(解释说明的意思)
就是鼠标悬停时出现的一个提示信息
API
|
说明
|
setToolTip
|
设置 toolTip. ⿏标悬停在该 widget 上时会有提⽰说明.
|
setToolTipDuring
|
设置 toolTip 提⽰的时间. 单位 ms. 时间到后 toolTip ⾃动消失
|
1.10 focusPolicy(设置控件获取到焦点)
设置控件获取到焦点的策略. ⽐如某个控件能否⽤⿏标选中或者能否通过 tab 键选中.
API |
说明
|
focusPolicy()
|
获取该 widget 的 focusPolicy, 返回 Qt::FocusPolicy
|
setFocusPolicy(Qt::FocusPolicy
policy
)
|
设置 widget 的 focusPolicy.
|
- Qt::NoFocus :控件不会接收键盘焦点
- Qt::TabFocus :控件可以通过Tab键接收焦点
- Qt::ClickFocus :控件在⿏标点击时接收焦点
- Qt::StrongFocus :控件可以通过Tab键和⿏标点击接收焦点 (默认值)
-
Qt::WheelFocus : 类似于 Qt::StrongFocus , 同时控件也通过⿏标滚轮获取到焦点 (新增的选项, ⼀般很少使⽤)
- 在ui界面上也可以直接更改
1.11 styleSheet
通过 CSS 设置 Qwidget 的样式.
- 也可以在ui界面上右键,然后再添加编辑样式表,
- 注意:这里的css样式别写错了,如果写错了Qt是不会报错的
2 按钮类
2.1 Pushbutton(普通按钮)
API | 说明 |
text
|
按钮中的⽂本
|
icon
|
按钮中的图标
|
iconSize
|
按钮中图标的尺⼨
|
shortCut
|
按钮对应的快捷键
|
autoRepeat
|
按钮是否会重复触发. 当⿏标左键按住不放时,
如果设为 true, 则会持续产⽣⿏标点击事件;
如果设为 false, 则必须释放⿏标, 再次按下⿏标时才能产⽣点击事件.
(相当于游戏⼿柄上的 "连发" 效果)
|
autoRepeatDelay
|
重复触发的延时时间. 按住按钮多久之后, 开始重复触发.
|
autoRepeatInterval |
重复触发的周期.
|
说明一下:
- QAbstractButton 作为 QWidget 的⼦类, 当然也继承了 QWidget 的属性. 上⾯
介绍的 QWidget ⾥的各种属性⽤法, 对于 QAbstractButton 同样适⽤
2.2 Radio Buttion(单选)
API | 说明 |
checkable
|
是否能选中
|
checked
|
是否已经被选中. checkable 是 checked 的前提条件
|
autoExclusive
|
是否排他.
选中⼀个按钮之后是否会取消其他按钮的选中.
对于
QRadioButton
来说
默认就是排他的
|
2.3 QButtonGroup(按钮组)
- QButtonGroup表示: 每⼀组内部来控制排他, 但是组和组之间不能排他
2.4 Check Box(多选)
2.5 Tool Button(工具按钮)
QToolButton 的⼤部分功能, 和 QPushButton 是⼀致的. 但是 QToolButton 主要应⽤在⼯具栏, 菜单等场景.
3 显示类
3.1 Lable(普通文本框)
属性
| 说明 |
text
|
QLabel 中的⽂本
|
textFormat
|
Qt::PlainText
纯⽂本
Qt::RichText 富⽂本(⽀持 html 标签)
Qt::MarkdownText
markdown 格式
Qt::AutoText
根据⽂本内容⾃动决定⽂本格式
|
pixmap
|
QLabel
内部
包含的图⽚
|
scaledContents
|
设为 true 表⽰内容⾃动拉伸填充
QLabel
设为 false 则不会⾃动拉伸
|
alignment
|
对⻬⽅式.
可以设置⽔平和垂直⽅向如何对⻬
|
wordWrap
|
设为 true 内部的⽂本会⾃动换⾏.
设为 false 则内部⽂本不会⾃动换⾏.
|
indent
|
设置⽂本缩进. ⽔平和垂直⽅向都⽣效
|
margin
|
内部⽂本和边框之间的边距.
不同于于 indent, 它是上下左右四个⽅向都同时有效.
⽽ indent 最多只是两个⽅向有效(具体哪两个⽅向有效取决于 alignment )
|
openExternalLinks
|
是否允许打开⼀个外部的链接.
(当 QLabel ⽂本内容包含 url 的时候涉及到)
|
buddy
|
给 QLabel 关联⼀个 "伙伴" , 这样点击 QLabel 时就能激活对应的伙伴.
例如伙伴如果是⼀个 QCheckBox, 那么该 QCheckBox 就会被选中
|
-
虽然 QPushButton 也可以通过设置图标的⽅式设置图⽚, 但是并⾮是⼀个好的选择. 更多的时候还是希望通过 QLabel 来作为⼀个 更单纯的显⽰图⽚ 的⽅式.
上述的案例存在一个问题,用户在拉伸窗口时,图片的size并没有随之改变,我们需要引入事件来解决
Qt中,表示用户的操作,有两类概念:一个是信号,另一个是事件~~
- 当用户拖拽窗口大小的时候,就会触发resize事件(resizeEvent)
- 像resize这样的事件,是连续变化的,把窗口尺寸从A拖到B这个过程中,就会触发一系列的resizeEvent,然后就可以借助resizeEvent来完成上述的功能
- 此处设置的缩进,即使文本换行了后续的行也会产生缩进,不仅仅是首行缩进
- 在QT中,QLabel中写的文本,是可以指定"快捷键",但这里的快捷键的规则功能上,要比QPushButton要弱一点
- 它表示快捷键的操作是 & 跟上一个字符来表示快捷键
比如: &A => 通过键盘上的alt+a来触发 - 而绑定了伙伴关系之后,通过快捷键就可以选中对应的单选按钮/复选按钮
3.2 LCD Number(LCD电子数字)
QLCDNumer 是⼀个专⻔⽤来显⽰数字的控件. 类似于 "⽼式计算器" 的效果.
属性
|
说明
|
intValue
|
QLCDNumber
显⽰的数字值(int).
|
value
|
QLCDNumber
显⽰的数字值(double). 和intValue 是联动的.
例如给 value 设为 1.5, intValue 的值就是 2.
另外,设置 value 和 intValue 的⽅法名字为
display
, ⽽不是
setValue
或 者 setIntValue
|
digitCount
|
显⽰⼏位数字.
|
mode
|
数字显⽰形式.
1.
QLCDNumber::Dec
:⼗进制模式,显⽰常规的⼗进制数字。
2.
QLCDNumber::Hex
:⼗六进制模式,以⼗六进制格式显⽰数字。
3.
QLCDNumber::Bin
:⼆进制模式,以⼆进制格式显⽰数字。
4.
QLCDNumber::Oct
:⼋进制模式,以⼋进制格式显⽰数字
只有⼗进制的时候才能显⽰⼩数点后的内容
|
segmentStyle
|
设置显⽰⻛格.
1.
QLCDNumber::Flat
:平⾯的显⽰⻛格,数字呈现在⼀个平坦的表⾯上。
2.
QLCDNumber::Outline
:轮廓显⽰⻛格,数字具有清晰的轮廓和阴影效果。
3.
QLCDNumber::Filled
:填充显⽰⻛格,数字被填充颜⾊并与背景区分开
|
smallDecimalPoin
|
设置⽐较⼩的 ⼩数点.
|
关于定时器
- 在C++标准库中,没有提供定时器的实现,但Boost里面提供了对应的功能
- 而在Qt中也封装了对应的定时器~~(结合了信号槽机制)
- 通过QTimer这个类创建处理的对象,就会产生一个timeout这样的信号
- 再结合connect,把这个timeout信号绑定到需要的槽函数中,就可以执行周期性的修改LCDNumber中的数字
不允许在新线程中修改界面的值
- 在Qt里面,界面中有一个专门的线程去负责维护更新(主线程->main函数所在的线程)
- 而对于GUI来说,内部包含了很多的隐藏状态,Qt为了保证修改界面的过程中,线程安全是不会受影响的,所以Qt就禁止了其他线程直接修改界面
ui->lcdNumer->display(value),形如这种操作,就是在修改界面 - 对于Qt的槽函数来说,默认情况下,槽函数都是由主线程调用的,在槽函数中修改界面是没有任何问题的
但要是自己创建了一个新线程,再修改界面就会出错,程序就会异常退出
3.3 ProgressBar(进度条)
属性 | 说明 |
minimum
|
进度条最⼩值
|
maximum
|
进度条最⼤值
|
value
|
进度条当前值
|
alignment
|
⽂本在进度条中的对⻬⽅式
Qt::AlignLeft
: 左对⻬
Qt::AlignRight
: 右对⻬
Qt::AlignCenter
: 居中对⻬
Qt::AlignJustify
: 两端对⻬
|
textVisible
|
进度条的数字是否可⻅.
|
orientation
|
进度条的⽅向是⽔平还是垂直
|
invertAppearance
|
是否是朝反⽅向增⻓进度
|
textDirection
|
⽂本的朝向
|
format
|
展⽰的数字格式.
%p
:表⽰进度的百分⽐(0-100)
%v
:表⽰进度的数值(0-100)
%m
:表⽰剩余时间(以毫秒为单位)
%t
:表⽰总时间(以毫秒为单位)
|
- 进度条的更新也是需要搭配定时器来完成的
3.4 Calendar Widget(日历)
属性 | 说明 |
selectDate
|
当前选中的⽇期
|
minimumDate
|
最⼩⽇期
|
maximumDate
|
最⼤⽇期
|
firstDayOfWeek
|
每周的第⼀天(也就是⽇历的第⼀列) 是周⼏
|
gridVisible
|
是否显⽰表格的边框
|
selectionMode
|
是否允许选择⽇期
|
navigationBarVisible
|
⽇历上⽅标题是否显⽰
|
horizontalHeaderFormat
|
⽇历上⽅标题显⽰的⽇期格式
|
verticalHeaderFormat
|
⽇历第⼀列显⽰的内容格式
|
dateEditEnabled
|
是否允许⽇期被编辑
|
信号
| 说明 |
selectionChanged(const QDate&)
|
当选中的⽇期发⽣改变时发出
|
activated(const QDate&)
|
当双击⼀个有效的⽇期或者按下回⻋键时发出, 形参是⼀个QDate类型,保存了选中的⽇期 |
currentPageChanged(int, int)
|
当年份⽉份改变时发出,形参表⽰改变后的新年份和⽉份
|
4. 输入类控件
4.1 Line Edit(单行输入框)
属性 | 说明 |
text
|
输⼊框中的⽂本
|
inputMask
|
输⼊内容格式约束
|
maxLength
|
最⼤⻓度
|
frame
|
是否添加边框
|
echoMode
|
显⽰⽅式.
•
QLineEdit::Normal
:这是默认值,⽂本框会显⽰输⼊的⽂本。
•
QLineEdit::Password
:在这种模式下,输⼊的字符会被隐藏,
通常⽤星号(*)或等号(=)代替。
•
QLineEdit::NoEcho
:在这种模式下,⽂本框不会显⽰任何输⼊
的字符
|
cursorPosition
|
光标所在位置
|
alignment
|
⽂字对⻬⽅式, 设置⽔平和垂直⽅向的对⻬
|
dragEnabled
|
是否允许拖拽
|
readOnly
|
是否是只读的(不允许修改)
|
placeHolderText
|
当输⼊框内容为空的时候, 显⽰什么样的提⽰信息
|
clearButtonEnabled
|
是否会⾃动显⽰出 "清除按钮"
|
核⼼信号
属性
|
说明
|
void cursorPositionChanged(int old, int new)
|
当⿏标移动时发出此信号,old为先前的位置,new为新位置
|
void editingFinished()
|
当按返回或者回⻋键时,或者⾏编辑失去焦点时,发出此信号
|
void returnPressed()
|
当返回或回⻋键按下时发出此信号.
如果设置了验证器, 必须要验证通过, 才能触发
|
void selectionChanged()
|
当选中的⽂本改变时,发出此信号
|
void textChanged(const
QString &text)
|
当QLineEdit中的⽂本改变时,发出此信号,text是新的⽂本。
代码对⽂本的修改
能够
触发这个信号
|
void textEdited(const QString
&text))
|
当QLineEdit中的⽂本改变时,发出此信号,text是新的⽂本。
代码对⽂本的修改
不能
触发这个信号.
|
-
inputMask 只能进⾏简单的输⼊格式校验.实际开发中, 基于 正则表达式 的⽅式是更核⼼的⽅法
要求在输⼊框中输⼊⼀个合法的电话号码(1 开头, 11 位, 全都是数字). 如果验证不通过, 则确定按钮⽆法点击
说明一下:
- 这里说明一下state validator(QString&,int &)函数的2个参数,
- 第一个参数是要验证的字符串,参数类型是QString & 不是const QString&,所以这里我创建了一个临时对象
- 第二个参数表示,如果这个字符串不符合规则,是从那个位置开始的
4.2 Text Edit(多行输入框)
QTextEdit 表⽰多⾏输⼊框. 也是⼀个富⽂本 & markdown 编辑器. 并且能在内容超出编辑框范围时⾃动提供滚动条
属性 | 说明 |
markdown
|
输⼊框内持有的内容. ⽀持 markdown 格式. 能够⾃动的对markdown ⽂本进⾏
渲染成 html
|
html
|
输⼊框内持有的内容. 可以⽀持⼤部分 html 标签. 包括 img 和 table 等
|
placeHolderText
|
输⼊框为空时提⽰的内容.
|
readOnly
| 是否是只读 |
undoRedoEnable
|
是否开启 undo / redo 功能.
按下 ctrl + z 触发 undo
按下 ctrl + y 触发 redo
|
autoFormating
|
开启⾃动格式化.
|
tabstopWidth
|
按下缩进占多少空间
|
overwriteMode
|
是否开启覆盖写模式
|
acceptRichText
|
是否接收富⽂本内容
|
verticalScrollBarPolicy
|
垂直⽅向滚动条的出现策略
Qt::ScrollBarAsNeeded
: 根据内容⾃动决定是否需要滚动条。这是默认值。
Qt::ScrollBarAlwaysOff
: 总是关闭滚动条。
Qt::ScrollBarAlwaysOn
: 总是显⽰滚动条。
|
horizontalScrollBarPolicy
|
⽔平⽅向滚动条的出现策略
Qt::ScrollBarAsNeeded
: 根据内容⾃动决定是否需要滚动条。这是默认值。
Qt::ScrollBarAlwaysOff
: 总是关闭滚动条。
Qt::ScrollBarAlwaysOn
: 总是显⽰滚动条
|
核心信号
核心属性 | 说明 |
textChanged()
|
⽂本内容改变时触发
|
selectionChanged()
|
选中范围改变时触发
|
cursorPositionChanged()
|
光标移动时触发
|
undoAvailable(bool)
|
可以进⾏ undo 操作时触发
|
redoAvailable(bool)
|
可以进⾏ redo 操作时触发
|
copyAvaiable(bool)
|
⽂本被选中/取消选中时触发
|
4.3 Combo Box(下拉选择框)
属性 | 说明 |
currentText
|
当前选中的⽂本
|
currentIndex
|
当前选中的条⽬下标 从 0 开始计算. 如果当前没有条⽬被选中, 值为 -1 |
editable
|
是否允许修改
设为 true 时,
QComboBox
的⾏为就⾮常接近
QLineEdit
, 也可以设置 validator
|
iconSize
|
下拉框图标 (⼩三⻆) 的⼤⼩
|
maxCount
|
最多允许有多少个条⽬
|
核⼼⽅法
⽅法
|
说明
|
addItem(const QString&)
|
添加⼀个条⽬
|
currentIndex()
|
获取当前条⽬的下标
从 0 开始计算. 如果当前没有条⽬被选中, 值为 -1
|
currentText()
|
获取当前条⽬的⽂本内容
|
方法 | 说明 |
activated(int)
activated(const QString & text)
|
当⽤⼾选择了⼀个选项时发出.
这个时候相当于⽤⼾点开下拉框, 并且⿏标划过某个选项.
此时还没有确认做出选择
|
currentIndexChanged(int)
currentIndexChanged(const QString
& text)
|
当前选项改变时发出.
此时⽤⼾已经明确的选择了⼀个选项.
⽤⼾操作或者通过程序操作都会触发这个信号
|
editTextChanged(const QString &
text)
|
当编辑框中的⽂本改变时发出
(editable 为 true 时有效)
|
说明一下:
- 下拉框里的条目,一般都是从网络/文件中读取的
- QString::fromStdString(s);// 把std::string 转换成QString
- s.toStdString();//把QString转换成std::string
4.4 Spin Box(微调框)
属性 | 说明 |
value
|
存储的数值
|
singleStep
|
每次调整的 "步⻓". 按下⼀次按钮数据变化多少
|
displayInteger
|
数字的进制. 例如 displayInteger 设为 10, 则是按照 10 进制表⽰. 设为 2 则为 2 进制表⽰
|
minimum
|
最⼩值
|
maximum
|
最⼤值
|
suffix
|
后缀
|
prefix
|
前缀
|
wrapping
|
是否允许换⾏
|
frame
|
是否带边框
|
alignment
|
⽂字对⻬⽅式.
|
readOnly
|
是否允许修改
|
buttonSymbol
|
按钮上的图标.
UpDownArrows
上下箭头形式
PlusMinus
加减号形式
NoButtons
没有按钮
|
accelerated (加速的)
|
按下按钮时是否为快速调整模式
|
correctionMode
|
输⼊有误时如何修正.
QAbstractSpinBox::CorrectToPreviousValue
: 如果⽤⼾输⼊了⼀个⽆效的值(例如,在只能显⽰正整数的SpinBox中输⼊了负数),那么SpinBox会恢复为上⼀个有效值。例如,如果SpinBox的初始值是1,⽤⼾
输⼊了-1(⽆效),然后SpinBox会恢复为1。
QAbstractSpinBox::CorrectToNearestValue
: 如果⽤⼾输⼊了⼀个 ⽆效的值,SpinBox会恢复为最接近的有效值。例如,如果SpinBox的初始值是1,⽤⼾输⼊了-1(⽆效),那么SpinBox会恢复为0
|
keyboardTrack
|
是否开启键盘跟踪.
设为 true, 每次在输⼊框输⼊⼀个数字, 都会触发⼀次 valueChanged() 和
textChanged() 信号.
设为 false, 只有在最终按下 enter 或者输⼊框失去焦点, 才会触发
valueChanged() 和 textChanged() 信号.
|
信号
|
说明
|
textChanged(QString)
|
微调框的⽂本发⽣改变时会触发. 参数 QString 带有 前缀 和 后缀.
|
valueChanged(int)
|
微调框的⽂本发⽣改变时会触发.
参数 int, 表⽰当前的数值
|
4.5 Date Edit & Time Edit (日期&时间微调框)
这⼏个控件⽤法⾮常相似, 这里以 QDateTimeEdit 为例进⾏介绍
属性 | 说明 |
dateTime
|
时间⽇期的值. 形如
2000/1/1 0:00:00
|
date
|
单纯⽇期的值. 形如
2001/1/1
|
time
|
单纯时间的值. 形如
0:00:00
|
displayFormat
|
时间⽇期格式. 形如
yyyy/M/d H:mm
•
y
表⽰年份
•
M
表⽰⽉份
•
d
表⽰⽇期
•
H
表⽰⼩时
•
m
表⽰分钟
•
s
表⽰秒
注意: 这⾥的格式化符号的含义, 不要记忆. 不同语⾔/库的设定规则
是存在差异的. ⼀定是⽤的时候再去查
|
minimumDateTime
|
最⼩时间⽇期
|
maximumDateTime
|
最⼤时间⽇期
|
timeSpec
|
Qt::LocalTime
:显⽰本地时间。
Qt::UTC
:显⽰协调世界时(UTC)。
Qt::OffsetFromUTC
:显⽰相对于UTC的偏移量(时差)
|
- UTC 时间是⼀个基于原⼦钟的标准时间
-
如我们使⽤的北京时间, 位于 "东⼋区", 就需要在 UTC 时间基础上 +8 个⼩时的时差
核⼼信号
信号
| 说明 |
dateChanged(QDate)
|
⽇期改变时触发.
|
timeChanged(QTime)
|
时间改变时触发.
|
dateTimeChanged(QDateTime)
|
时间⽇期任意⼀个改变时触发.
|
4.6 Dial(旋转按钮)
属性 | 说明 |
value
|
持有的数值.
|
minimum
|
最⼩值
|
maximum
|
最⼤值
|
singleStep
|
按下⽅向键的时候改变的步⻓
|
pageStep
|
按下 pageUp / pageDown 的时候改变的步⻓
|
sliderPosition
|
界⾯上旋钮显⽰的 初始位置
|
tracking
|
外观是否会跟踪数值变化.
默认值为 true. ⼀般不需要修改.
|
wrapping
|
是否允许循环调整.
即数值如果超过最⼤值, 是否允许回到最⼩值.
(调整过程能否 "套圈")
|
notchesVisible
|
是否显⽰ 刻度线
|
notchTarget
|
刻度线之间的相对位置.
数字越⼤, 刻度线越稀疏
|
核心信号
属性 | 说明 |
valueChanged(int)
|
数值改变时触发
|
rangeChanged(int, int)
|
范围变化时触发
|
4.7 Slider(滑动条)
属性
| 说明 |
value
|
持有的数值.
|
minimum
|
最⼩值
|
maximum
|
最⼤值
|
singleStep
|
按下⽅向键的时候改变的步⻓
|
pageStep
|
按下 pageUp / pageDown 的时候改变的步⻓
|
sliderPosition
|
滑动条显⽰的 初始位置
|
tracking
|
外观是否会跟踪数值变化.
默认值为 true. ⼀般不需要修改
|
orientation
|
滑动条的⽅向是⽔平还是垂直
|
invertedAppearance
|
是否要翻转滑动条的⽅向
|
tickPosition
|
刻度的位置
|
tickInterval
|
刻度的密集程度
|
核⼼信号
属性
|
说明
|
valueChanged(int)
|
数值改变时触发
|
rangeChanged(int, int)
|
范围变化时触发
|
- 通过这些设置,就可以让滑动条控制拉伸窗口的大小
- 也可以通过QShortcut这里个类实现快捷键,并connect槽函数
5. 多元素控件
-
QListWidget QListView 列表
-
QTableWidget QTableView 表格
-
QTreeWidget QTreeView 树形
5.0 xxWidget vs xxView是什么区别
-
xxView是MVC结构的一种典型实现,MVC是软件开发中,非常经典的软件结构组织形式
M model数据,V view 视图(界面),C controller控制器 数据和视图之间的业务流程 -
xxView只是负责实现了视图,不负责数据如何存储表示,更不负责数据和视图之间的交互
-
而xxWidget是基于xxView封装而来的,同时吧model和controller都帮我们实现好了
可以直接使用,它提供了功能很方便的api,让我们直接使用
5.1 List Widget(纵向列表)
属性 | 说明 |
currentRow
|
当前被选中的是第⼏⾏
|
count
|
⼀共有多少⾏
|
sortingEnabled
|
是否允许排序
|
isWrapping
|
是否允许换⾏
|
itemAlignment
|
元素的对⻬⽅式
|
selectRectVisible
|
被选中的元素矩形是否可⻅
|
spacing
|
元素之间的间隔
|
核⼼⽅法
⽅法
| 说明 |
addItem
(const QString& label)
addItem(QListWidgetItem *
item)
|
列表中添加元素
|
currentItem()
| 返回 QListWidgetItem* 表⽰当前选中的元素 |
setCurrentItem(QListWidgetItem* item)
|
设置选中哪个元素
|
setCurrentRow(int row)
|
设置选中第⼏⾏的元素
|
insertItem(const QString& label, int row)
insertItem(QListWidgetItem *
item, int
row)
|
在指定的位置插⼊元素
|
item(int row)
|
返回 QListWidgetItem* 表⽰第 row ⾏的元素
|
takeItem(int row)
|
删除指定⾏的元素, 返回 QListWidgetItem* 表⽰是哪个元素被删
除了
|
核心信号
方法 | 说明 |
currentItemChanged(QListWidgetItem*
current, QListWidgetItem* old)
|
选中不同元素时会触发. 参数是当前选中的元素和之前选中的元素
|
currentRowChanged(int)
|
选中不同元素时会触发. 参数是当前选中元素的⾏数.
|
itemClicked(QListWidgetItem* item)
|
点击某个元素时触发
|
itemDoubleClicked(QListWidgetItem* item)
|
双击某个元素时触发
|
itemEntered(QListWidgetItem* item)
|
⿏标进⼊元素时触发
|
- 对于添加条目,可以直接通过图形化界面的方式
- 使用QListWidget时,记得包含头文件<QListWidget>
5.2 Table Widget(表单)
TableWidget控件相当于二维ListWidget
属性 | 说明 |
item(int row, int column)
|
根据⾏数列数获取指定的
QTableWidgetItem*
|
setItem(int row, int column,
QTableWidget*)
|
根据⾏数列数设置表格中的元素
|
currentItem()
|
返回被选中的元素 QTableWidgetItem*
|
currentRow()
|
返回被选中元素是第⼏⾏
|
currentColumn()
|
返回被选中元素是第⼏列
|
row(QTableWidgetItem* )
|
获取指定 item 是第⼏⾏
|
column(QTableWidgetItem* )
|
获取指定 item 是第⼏列
|
rowCount()
|
获取⾏数
|
columnCount()
|
获取列数
|
insertRow(int row)
|
在第 row ⾏处插⼊新⾏
|
insertColumn(int column)
|
在第 column 列插⼊新列
|
removeRow(int row)
|
删除第 row ⾏
|
removeColumn(int column) |
删除第 column 列
|
setHorizontalHeaderItem(int column, QTableWidget*)
|
设置指定列的表头
|
setVerticalHeaderItem(int row,
QTableWidget*)
|
设置指定⾏的表头
|
核⼼信号
信号
| 说明 |
cellClicked(int row, int column)
|
点击单元格时触发
|
cellDoubleClicked(int row, int
column)
|
双击单元格时触发
|
cellEntered(int row, int column)
|
⿏标进⼊单元格时触发
|
currentCellChanged(int row, int column, int previousRow, int previousColumn)
|
选中不同单元格时触发
|
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
ui->lineEdit->setPlaceholderText("请输入一列名");
ui->lineEdit_2->setPlaceholderText("请输入一行名");
// 初始化表单
// 创建 3 行
ui->tableWidget->insertRow(0);
ui->tableWidget->insertRow(1);
ui->tableWidget->insertRow(2);
// 给 3 行设定行名
ui->tableWidget->setVerticalHeaderItem(0, new QTableWidgetItem("NO.1"));
ui->tableWidget->setVerticalHeaderItem(1, new QTableWidgetItem("NO.2"));
ui->tableWidget->setVerticalHeaderItem(2, new QTableWidgetItem("NO.3"));
// 创建 3 列
ui->tableWidget->insertColumn(0);
ui->tableWidget->insertColumn(1);
ui->tableWidget->insertColumn(2);
// 给 3 列设定列名
ui->tableWidget->setHorizontalHeaderItem(0, new QTableWidgetItem("学号"));
ui->tableWidget->setHorizontalHeaderItem(1, new QTableWidgetItem("姓名"));
ui->tableWidget->setHorizontalHeaderItem(2, new QTableWidgetItem("年龄"));
// 设置初始数据
ui->tableWidget->setItem(0, 0, new QTableWidgetItem("1001"));
ui->tableWidget->setItem(0, 1, new QTableWidgetItem("张三"));
ui->tableWidget->setItem(0, 2, new QTableWidgetItem("20"));
ui->tableWidget->setItem(1, 0, new QTableWidgetItem("1002"));
ui->tableWidget->setItem(1, 1, new QTableWidgetItem("李四"));
ui->tableWidget->setItem(1, 2, new QTableWidgetItem("21"));
ui->tableWidget->setItem(2, 0, new QTableWidgetItem("1003"));
ui->tableWidget->setItem(2, 1, new QTableWidgetItem("王五"));
ui->tableWidget->setItem(2, 2, new QTableWidgetItem("19"));
}
Widget::~Widget()
{
delete ui;
}
void Widget::on_pushButton_addrow_clicked()
{
int row = ui->tableWidget->rowCount();
ui->tableWidget->insertRow(row);
const QString& name = ui->lineEdit_2->text();
ui->tableWidget->setVerticalHeaderItem(row,new QTableWidgetItem(name));
}
void Widget::on_pushButton_addcolum_clicked()
{
int col = ui->tableWidget->columnCount();
ui->tableWidget->insertColumn(col);
const QString& name = ui->lineEdit->text();
ui->tableWidget->setHorizontalHeaderItem(col,new QTableWidgetItem(name));
}
void Widget::on_pushButton_delrow_clicked()
{
int row = ui->tableWidget->currentRow();
ui->tableWidget->removeRow(row);
}
void Widget::on_pushButton_delcolum_clicked()
{
int cur = ui->tableWidget->currentColumn();
ui->tableWidget->removeColumn(cur);
}
5.3 Tree Widget (树状形节点表单)
属性 | 说明 |
clear
|
清空所有⼦节点
|
addTopLevelItem(QTreeWidgetItem* item)
|
新增顶层节点
|
topLevelItem(int index)
|
获取指定下标的顶层节点.
|
topLevelItemCount()
|
获取顶层节点个数
|
indexOfTopLevelItem(QTreeWidgetItem*
item)
|
查询指定节点是顶层节点中的下标
|
takeTopLevelItem(int index)
|
删除指定的顶层节点. 返回 QTreeWidgetItem* 表⽰被删除
的元素
|
currentItem()
|
获取到当前选中的节点, 返回 QTreeWidgetItem*
|
setCurrentItem(QTreeWidgetItem* item)
|
选中指定节点
|
setExpanded(bool)
|
展开/关闭节点
|
setHeaderLabel(const QString& text)
|
设置 TreeWidget 的 header 名称.
|
核心方法
方法 | 说明 |
addChild(QTreeWidgetItem* child)
|
新增⼦节点
|
childCount()
|
⼦节点的个数
|
child(int index)
|
获取指定下标的⼦节点. 返回 QTreeWidgetItem*
|
takeChild(int index)
|
删除对应下标的⼦节点
|
removeChild(QTreeWidgetItem*
child)
|
删除对应的⼦节点
|
parent()
|
获取该元素的⽗节点
|
核⼼信号
信号 | 说明 |
currentItemChanged(QTreeWidgetItem*
current, QTreeWidgetItem* old)
|
切换选中元素时触发
|
itemClicked(QTreeWidgetItem* item, int col)
|
点击元素时触发
|
itemDoubleClicked(QTreeWidgetItem* item, int col)
|
双击元素时触发
|
itemEntered(QTreeWidgetItem* item, int col)
|
⿏标进⼊时触发
|
itemExpanded(QTreeWidgetItem* item)
|
元素被展开时触发
|
itemCollapsend(QTreeWidgetItem* item)
|
元素被折叠时触发
|
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
ui->treeWidget->setHeaderLabel("动物");
QTreeWidgetItem* item1 = new QTreeWidgetItem();
item1->setText(0, "猫");
ui->treeWidget->addTopLevelItem(item1);//添加顶层节点
QTreeWidgetItem* item2 = new QTreeWidgetItem();
item2->setText(0, "狗");
ui->treeWidget->addTopLevelItem(item2);//添加顶层节点
QTreeWidgetItem* item3 = new QTreeWidgetItem();
item3->setText(0, "鸟");
ui->treeWidget->addTopLevelItem(item3);//添加顶层节点
}
Widget::~Widget()
{
delete ui;
}
void Widget::on_pushButton_clicked()
{
const QString& text = ui->lineEdit->text();
if(text.isEmpty()){
return;
}
// 添加到顶层节点中
QTreeWidgetItem* item = new QTreeWidgetItem();
item->setText(0,text);
ui->treeWidget->addTopLevelItem(item);
}
void Widget::on_pushButton_2_clicked()
{
const QString& text = ui->lineEdit->text();
if (text.isEmpty()) {
return;
}
// 获取到当前选中的节点
QTreeWidgetItem* currentItem = ui->treeWidget->currentItem();
if (currentItem == NULL) {
return;
}
// 构造新的 item
QTreeWidgetItem* newItem = new QTreeWidgetItem();
newItem->setText(0, text);
// 添加 item 到选中节点
currentItem->addChild(newItem);
// 展开父节点
currentItem->setExpanded(true);
}
void Widget::on_pushButton_3_clicked()
{
QTreeWidgetItem* cur = ui->treeWidget->currentItem();
if(cur == NULL){
return;
}
// 获取当前节点的父节点
QTreeWidgetItem* parent = cur->parent();
if(parent == NULL){
// 顶层节点
// 查询指定节点是顶层节点中的下标
int index = ui->treeWidget->indexOfTopLevelItem(cur);
ui->treeWidget->takeTopLevelItem(index);
}
else{
// 非顶层节点
parent->removeChild(cur);
}
}
- QTreeWidget控件虽然是树形结构,但是这个树形结构,没有体现出根节点,
是从根节点的下一次子节点开始计算的 - 针对顶层节点来说,这里也是一个类似于"List"这样的结构
6 容器类控件
6.1 Group Box(控件组)
属性
|
说明
|
title
|
分组框的标题
|
alignment
|
分组框内部内容的对⻬⽅式
|
flat
|
是否是 "扁平" 模式
|
checkable
|
是否可选择.
设为 true, 则在 title 前⽅会多出⼀个可勾选的部分
|
checked
|
描述分组框的选择状态 (前提是 checkable 为 true)
|
6.2 Tab Widget(多页面)
属性 | 说明 |
tabPosition
|
标签⻚所在的位置.
•
North
上⽅
•
South
下⽅
•
West
左侧
•
East
右侧
|
currentIndex
|
当前选中了第⼏个标签⻚ (从 0 开始计算)
|
currentTabText
|
当前选中的标签⻚的⽂本
|
currentTabName
|
当前选中的标签⻚的名字
|
currentTabIcon
|
当前选中的标签⻚的图标
|
currentTabToolTip
|
当前选中的标签⻚的提⽰信息
|
tabsCloseable
|
标签⻚是否可以关闭
|
movable
|
标签⻚是否可以移动
|
属性
|
说明
|
currentChanged(int)
|
在标签⻚发⽣切换时触发, 参数为被点击的选项卡编号
|
tabBarClicked(int)
|
在点击选项卡的标签条的时候触发. 参数为被点击的选项卡编号
|
tabBarDoubleClicked(int)
|
在双击选项卡的标签条的时候触发. 参数为被点击的选项卡编号.
|
tabCloseRequest(int)
|
在标签⻚关闭时触发. 参数为被关闭的选项卡编号.
|

-
Qt 中使⽤ ⽗⼦关系 决定控件的相对位置
7 布局控件
7.1 QVBoxLayout(垂直布局)
Layout 只是⽤于界⾯布局, 并没有提供信号
属性
|
说明
|
layoutLeftMargin
|
左侧边距
|
layoutRightMargin
|
右侧边距
|
layoutTopMargin
|
上⽅边距
|
layoutBottomMargin
|
下⽅边距
|
layoutSpacing
|
相邻元素之间的间距
|
- 界⾯上的按钮就存在于布局管理器中. 随着窗⼝尺⼨变化⽽发⽣改变
说明一下:
- 界面上的按钮不会随窗口尺寸变化而发生改变
- 通过 Qt Designer 创建的布局管理器, 其实是先创建了⼀个 widget, 设置过 geometry 属性
的. 再把这个 layout 设置到这个 widget 中.
-
实际上, ⼀个 widget 只能包含⼀个 layout.
7.2 QHBoxLayout(水平布局)
Layout 只是⽤于界⾯布局, 并没有提供信号
属性
|
说明
|
layoutLeftMargin
|
左侧边距
|
layoutRightMargin
|
右侧边距
|
layoutTopMargin
|
上⽅边距
|
layoutBottomMargin
|
下⽅边距
|
layoutSpacing
|
相邻元素之间的间距
|
- 界⾯上的按钮就存在于布局管理器中. 随着窗⼝尺⼨变化⽽发⽣改变
- 布局控件是可以嵌套的所以 结合 QHBoxLayout 和 QVBoxLayout , 就可以做出各种复杂的界⾯了
7.3 QGridLayout(网格布局)
属性 | 说明 |
layoutLeftMargin
|
左侧边距
|
layoutRightMargin
|
右侧边距
|
layoutTopMargin
|
上⽅边距
|
layoutBottomMargin
|
下⽅边距
|
layoutHorizontalSpacing
|
相邻元素之间⽔平⽅向的间距
|
layoutVerticalSpacing
|
相邻元素之间垂直⽅向的间距
|
layoutRowStretch
|
⾏⽅向的拉伸系数
|
layoutColumnStretch
|
列⽅向的拉伸系数
|
- 使⽤ addWidget 添加控件到布局管理器中. 但是添加的同时会指定两个坐标. 表⽰放在第⼏⾏, 第⼏列
-
任意调整⾏列, 即可看到不同的效果
-
注意: 如果要设置垂直方法的拉伸 直接 setRowStretch 效果不明显, 因为每个按钮的⾼度是固定的. 需要
-
把按钮的垂直⽅向的 sizePolicy 属性设置为 QSizePolicy::Expanding 尽可能填充满布局管理器, 才能看到效果
btn1->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
7.4 QFormLayout(表单布局)
属于是 QGridLayout 的特殊情况, 专⻔⽤于实现两列表单的布局
这种表单布局多⽤于让⽤⼾填写信息的场景. 左侧列为提⽰, 右侧列为输⼊框
7.5 Spacer(空白布局)
使⽤布局管理器的时候, 可能需要 在控件之间, 添加⼀段空⽩ . 就可以使⽤ QSpacerItem 来表⽰
属性 | 说明 |
width
|
宽度
|
height
|
⾼度
|
hData
|
⽔平⽅向的 sizePolicy
•
QSizePolicy::Ignored
: 忽略控件的尺⼨,不对布局产⽣影响。
•
QSizePolicy::Minimum
: 控件的最⼩尺⼨为固定值,布局时不会超过该值。
•
QSizePolicy::Maximum
: 控件的最⼤尺⼨为固定值,布局时不会⼩于该值。
•
QSizePolicy::Preferred
: 控件的理想尺⼨为固定值,布局时会尽量接近该
值。
•
QSizePolicy::Expanding
: 控件的尺⼨可以根据空间调整,尽可能占据更多空
间。
•
QSizePolicy::Shrinking
: 控件的尺⼨可以根据空间调整,尽可能缩⼩以适应
空间。
|
vData
|
垂直⽅向的 sizePolicy 选项同上.
|
- 调整 QSpacerItem 不同的尺⼨, 即可看到不同的间距
第六章 QT窗口
Qt 窗⼝ 是通过 QMainWindow 类 来实现的
-
菜单栏 (menu bar)
-
⼯具栏 (tool bars)
-
浮动窗⼝ ( 铆 接部件)(dock widgets)
-
中⼼部件 (central widget)
-
状态栏( status bar)
1. 菜单栏
⼀个主窗⼝最多只有⼀个菜单栏
1.1 创建菜单栏&&菜单&&菜单项
- 这里的菜单项和后面的工具栏的工具是同一个类,都是QAction
1.2 添加快捷键 && 链接信号槽
- 菜单项点击的时候会触发triggered信号,和前面那个信号类似
1.3 创建子菜单
- 就是在菜单中嵌套另一个菜单,就叫做子菜单
1.4 设置菜单项图标
说明一下:
- 如果给菜单也加上图标的话,就会把菜单的text覆盖掉
1.5 关于正确创建QMenuBar类
说明一下:
- 如果我们之前创建的项目,没有勾选ui文件,此时这2种代码都是正确的
- 但是如果勾选了自动生成ui文件,那么第2种代码就会引起内存泄漏,因为Qt已经自动生成了一个QMenuBar(在ui文件中,各个类的结构中可以看到),同时后面的QStatusBar也是一样的问题
2. 工具栏
⼯具栏是应⽤程序中集成各种功能实现快捷键使⽤的⼀个区域
2.1 创建工具栏
说明一下:
- 工具栏是可以有多个,并且可以像浮动窗口一样浮动和移动的
2.2 设置工具栏停靠位置
说明一下:
- Qt::LeftToolBarArea 停靠在左侧
- Qt::RightToolBarArea 停靠在右侧
- Qt::TopToolBarArea 停靠在顶部
- Qt::BottomToolBarArea 停靠在底部
- Qt::AllToolBarAreas 以上四个位置都可停靠
- Qt中很多枚举类型值,都不建议死记,建议不清楚的时候查查文档
3. 状态栏
状态栏是应⽤程序中输出简要信息的区域
3.1 关于正确创建工具栏
说明一下:
- addWidget是添加子控件的意思,
- 推荐使用this->statusBar()创建/获得对象,
4 滑动窗口
4.1 创建滑动窗口
说明一下:
-
浮动窗口内部, 添加一些其他的控件,不能直接给这个浮动窗口添加子控件, 而是需要创建出一个单独的 QWidget, 把要添加的控件加入到 QWidget 中
-
然后再把这个 QWidget 设置到 dockWidget 中.
5. 对话框
5.1 创建对话框
- QDialog不同于其他控件,此处QDialog每次按下按钮,都会创建一个新的QDialog对象
- 而一个程序运行过程中,可能会无数次触发点击这个按钮,就会产生无数个这样的对象
不好好处理,就会出现内存泄漏的问题,
说明一下:
- 而Qt为了方便我们使用,就封装了一层,在实际使用的时候,只要给dialog设置上下面这个属性
就会在关闭的时候自动进行delete(这个属于Qt内置的功能) -
dialog->setAttribute(Qt::WA_DeleteOnClose);
5.2 自定义对话框
说明一下:
- 想要自定义对话框,就需要继承自QDialog创建类,这里是通过代码自定义的
说明一下:
- 这个操作会创建出一个ui文件以及对应的类
- 通过图形化的方式创建会比代码创建的更快一些,因为会自动生成ui文件
5.3 模态与非模态
说明一下:
- dialog->show() 非模态: 弹出对话框的时候,用户可以操作父窗口
- dialog->exec() 模态: 弹出对话框的时候,用户无法操作父窗口
- Qt 提供了多种可复⽤的对话框类型,即 Qt 标准对话框。Qt 标准对话框全部继承于 QDialog类
5.4 QMessageBox(消息对话框)
Question
|
⽤于正常操作过程中的提问
|
Information
|
⽤于报告正常运⾏信息
|
Warning
|
⽤于报告⾮关键错误
|
Critical
|
⽤于报告严重错误
|
说明一下:
- QMessageBox 使用场景更多的是模态的(无法操作父窗口)
- 也可以通过QMessageBox中的静态成员函数,快速生成消息对话框
5.5 QColorDialog(调色板对话框)
- 直接调用QColorDialog静态成员函数生成对象是最快的
5.6 QFileDialog(文件对话框)
说明一下:
- 此处的打开/保存这里的功能都是需要额外去实现的,并不是直接一保存就保存好了
- QFileDialog::getOpenFileName的返回值是QString,是文件路径
- 但如果是QFileDialog::getOpenFileNames打开一堆文件,那么它的返回值就是QStringList,会保存一堆文件
5.7 QInputDialog(输入对话框)
5.8 QFontDialog(字体对话框)
6. 案例(动手)
自己实现一个图片预览器,有时间的话可以自己再研究研究,其实在用qt画客户端界面就是调函数,调函数,基本无脑,选中控件+F1进行帮助界面中,一个一个的查就行了
预览如下图:
要求说明:
- 需要一个按钮能够打开一个图片,或一堆图片,并把图片名显示在左侧列表中
- 右侧也能预览这个图片
- 还需要实现上一张 下一张 放大 缩小 左旋 右旋 删除
- 还有记得对图片去重,就是不能打开已存在列表中的图片
6.1 关键点 :分离/隐藏文件路径 和 文件名
由于后面不仅是需要使用图片的文件名,还有图片路径的,所以在打开文件时,是需要记录和保存这个2个值的
// 隐藏文件路径,包含去重处理
void MainWindow::HideFilePath(QString filePath)
{
QFileInfo fileInfo(filePath);//记录文件信息
// 创建一个新的条目,并隐藏文件路径
QListWidgetItem * fileMap = new QListWidgetItem();//创建一个条目
fileMap->setText(fileInfo.fileName());// 文件名
fileMap->setData(Qt::UserRole,filePath);//隐藏文件路径
// 去重:查找是否已有相同文件名的条目
// 第二个参数为查找完全匹配准确度,其中Qt::MatchExactly为完全匹配
QList<QListWidgetItem*> existingItems = ui->list_picture->findItems(fileInfo.fileName(), \
Qt::MatchExactly);
if (existingItems.isEmpty()) {
// 不存在相同的文件名,则添加
ui->list_picture->addItem(fileMap);
return;
}
}
第七章 事件
1. 概念
事件:是用户和应用软件间产生的一个 交互 操作,由用户操作产生或者系统内部产生,通过 事件循环 对事件进行处理,事件也可以用来在对象间进行信息交互
信号槽 : 用户进行的各项操作,就可能会产生出信号,可以给某个信号指定槽函数,当信号触发时,就能够自动的执行到对应的槽函数
事件: 用户进行的各种操作,也会产生事件,程序员同样可以给事件关联上处理函数(处理的逻辑),当事件触发的时候,就能够执行到对应的代码
总结: 信号槽就是对于事件的进一步封装,事件是信号槽的底层机制
在Qt平台中会将系统产生的消息转换为Qt事件 .事件本质上就是一个QEvent的对象
2. 为什么会出现事件
在实际Qt开发程序的过程中,绝大部分和用户之间进行的交互都是通过"信号槽"来完成的
但是在有些特殊情况下,信号槽不一定能搞定(某个用户的动作行为,Qt没有提供对应的信号)
此时就需要通过重写事件处理函数的形式,来手动处理事件的响应逻辑
让当前的类,重写某个事件处理函数,这里用到的是"多态"机制,创建子类,继承自Qt中已有的类
再在子类中重写父类的事件处理函数,后续事件触发的过程中,就会通过多态这样的机制,执行到我们自己写的子类函数中
3. 事件处理流程
3.1 事件产生
用户操作的各种操作都会产生事件,比如鼠标点击,键盘输入,拖动窗口,而在qt中会将所有的事件包装成一个QEvent对象,并将对象放入事件队列中
3.2 事件分发/事件过滤器
Qt 的事件循环将事件分发给目标对象前,会检查目标对象是否安装了事件过滤器,
当安装了:这个事件就需要先经过事件过滤器eventFilter(),事件能否向下进行,是需要通过eventFilter()返回值判断的
当返回值为true:表明事件已经被处理完毕了,不会向下传递了
当返回值为false:表明事件还没有处理完毕,会向下执行,并将事件分发给目标对象
当没有安装:这个事件就直接交给目标对象,通常是窗口控件等
3.3 事件处理
走到这一步,表明该事件没有被过滤器拦截,它将被传递给目标对象的 event()
函数
当事件已经交给目标对象后,还需要判读目标对象是否存在处理这个事件的函数
存在时:直接调用对应的事件处理函数处理
不存在时:该事件就会沿着继承链继续向上传递,交给目标对象的父对象去处理,以此类推
4. 代码演示
4.1 演示一: 绑定了信号与槽的控件 又重写了相同的事件函数
首先新建一个mainwindow窗口的函数,并在ui界面中添加一个按钮,在转到槽,实现点击信号的槽函数,这个槽函数中实现打印一句话就行了
之后再在项目中新建一个mypushbutton类继承QPushbutton
再将ui界面中的按钮类型提升为我们自定义的mypushbutton类型
运行项目得到:
说明一下:
- 这个按钮此时是绑定了信号与槽的,而打印结果和我们预期一样
接下来我们在子类中重写鼠标按下事件
然后再运行程序:
通过打印结果我们发现,这个按钮本来绑定的信号槽没有触发了,而是执行的重写鼠标按下事件的函数
结论/总结
当目标对象重写了事件处理函数以后,原本的槽函数没有被触发,这是因为在我们当前这个事件处理函数中,根本就没有发送信号
而之前被触发,是因为父类的QPushButton中的event()在发送信号,而在这里如果想要触发槽函数,可以通过:
方式一:自己手动发送信号(这里是emit clicked())
方式二(推荐):调用父类的事件处理函数(这里是QPushButton::mousePressEvent(event);)则父类就会帮我们发送信号
4.2 演示二:事件过滤器使用
补:事件过滤器,就是对指定事件进行过滤,增强事件处理函数
和上面一样还是先创建一个自定义的类MyEvenFilter
说明一下:
- 在我们自定义的事件过滤器对象中,必须重写eventFilter()事件过滤函数
- watched参数表示:目标对象
- event参数表示:事件对象
接下来就是在主窗口中安装这个事件过滤器了,不过这个需要先在主窗口中创建一个我们自定义类的对象(就是添加一个我们自定义类型的成员函数),方便调用
说明一下:
- 安装事件过滤器的函数是installEventFilter
运行演示
接下来直接运行项目:
- 此时事件过滤器,没有过滤到鼠标按下的事件,然后就直接返回true,自然连按钮也不会出现了,同理信号槽也没有用了,按钮中重写的事件也没有用了
- 但是当我直接return false;就会继续向下执行事件
- 上面代码表示捕捉鼠标按下事件,[进行增强处理], return ture;不再先下执行
- 同理如果改成return fasle;就会向下处理
推荐返回父类的事件过滤器
推荐返回值返回时:
- 直接调用父类的事件过滤器
5. 事件VS信号
项目 | 信号/槽 | 事件 |
触发 | 手动 emit | Qt 自动分发(系统或用户触发) |
响应 | connect 后自动调用槽函数 | 重写事件函数(如 mousePressEvent ) |
用途 | 对象通信,逻辑处理 | 用户输入、窗口变化等底层操作 |
6. QEventLoop
QEventLoop是Qt框架中的一个核心组件,用于实现局部或临时的事件循环。Qt中,主要的事件循环是由QCoreApplication
或QGuiApplication
提供的
6.1 在某个函数中,期望5s后在继续执行后面逻辑
方式一:QThread::sleep
说明一下:
- sleep()这种方式会卡死整个页面,它会将程序阻塞到这里,导致其他操作都无法执行
方式二:QEventLoop事件循环
说明一下:
- 可以配合定时器,当到一定时间后就发送信号,并关联时的发送退出事件循环信号
- 使用QEventLoop事件循环机制将不会阻塞其他程序执行
6.2 实现一个模态对话框
模态:当弹出新窗口时,无法对父窗口进行任何操作,
--- 首先新建一个自定义类,然后让它继承自QDialog类
---- 再在页面中新建按钮,并关联点击信号,打开对话框
说明一下:
- 对应时间循环可以简单的理解为一个死循环,不过里面不仅仅只是死循环
7. 常见事件
7.1 QMouseEvent(鼠标事件)
#ifndef LABEL_H
#define LABEL_H
#include <QWidget>
#include <QLabel>
#include <QMouseEvent>
class Label : public QLabel
{
Q_OBJECT
public:
Label(QWidget* parent);
void mousePressEvent(QMouseEvent *event);// 按下
void mouseReleaseEvent(QMouseEvent *event);// 释放
void mouseDoubleClickEvent(QMouseEvent *event);// 双击
};
#endif // LABEL_H
#include "label.h"
#include <QDebug>
Label::Label(QWidget* parent) : QLabel(parent)
{
;
}
void Label::mousePressEvent(QMouseEvent *event)
{
if(event->button() == Qt::LeftButton){
//qDebug() << "按下左键";
this->setText("按下左键");
}
else if(event->button() == Qt::RightButton){
qDebug() << "按下右键";
}
}
void Label::mouseReleaseEvent(QMouseEvent *event)
{
if(event->button() == Qt::LeftButton){
qDebug() << "释放左键";
}
else if(event->button() == Qt::RightButton){
qDebug() << "释放右键";
}
}
void Label::mouseDoubleClickEvent(QMouseEvent *event)
{
if(event->button() == Qt::LeftButton){
qDebug() << "双击左键";
}
else if(event->button() == Qt::RightButton){
qDebug() << "双击右键";
}
}
这里需要再ui界面中把这个控件提升为我们自己写的类,具体操作是: 选中原来的控件,鼠标右键,提升,选择新的类型,
说明一下:
- 这里是针对QLabel重写的事件,自然也只能在label控件中看到效果
- 这里是在widget中重写的事件,则这个大窗口都会有效果
- Qt为了保证程序的流畅性,默认情况下不会对鼠标移动进行追踪,鼠标移动的时候不会调用
mouseMoveEvent,除非显示告诉Qt要追踪鼠标位置
7.2 QWheelEvent(鼠标滚轮事件)
7.3 QKeyEvent(键盘事件)
7.4 QTimerEvent(时间事件)
说明一下:
- 此处的timerId类似于linux中的文件描述符
- QTimer的背后是QTimerEvent定时器事件进行支持的,所以要实现定时器,通常使用QTimer
7.5 QMoveEvent(窗口移动事件)
7.6 QResizeEvent(窗口尺寸事件)
第八章 进程 && 线程
1. 进程
QProcess类:额外执行新的程序,执行程序就是一个新的进程执行
QProcess:进程管理类,使用QProcess类可以操作进程
start(程序的路径):启动进程
1.1 QProcess入门
#include "mainwindow.h"
#include <QApplication>
#include <QProcess>
#include <QDebug>
#include <QTextCodec>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
// 打开一个记事本进程
// 实例化进程对象
QProcess process;
// 执行进程命令
//process.start("notepad");
// 执行ping 进程操作
// process.start("ping www.baidu.com");
process.start("ping",QStringList() <<"www.baidu.com");
// 获取进程返回值
// process.waitForFinished(5000) = 如果进程在5s之内没有执行结束,没有获取返回值,就说明该进程执行失败
if(process.waitForFinished())
{
QByteArray data = process.readAllStandardOutput();
QByteArray error = process.readAllStandardError();
// 获取系统默认字符编码
QTextCodec *codec = QTextCodec::codecForName("System");
QString result = codec->toUnicode(data);
qDebug() << result;
}
return a.exec();
}
1.2 进程间通信
本地:1. 共享内存 2. 共享文件
非本地:1.网络 2. 数据库
1.3 共享文件
新建读内存项目
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QSharedMemory>
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private slots:
void on_btn_read_clicked();
private:
Ui::MainWindow *ui;
QSharedMemory *memory;
};
#endif // MAINWINDOW_H
说明一下:
- 读共享内存时,只需要关联相同的共享内存就行了,即名字要相同
新建写内存项目
mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QSharedMemory>
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private slots:
void on_btn_write_clicked();
private:
Ui::MainWindow *ui;
QSharedMemory *memory;
};
#endif // MAINWINDOW_H
mainwindow.cpp
说明一下:
- 写共享内存时,需要开辟共享内存的大小
程序运行
注意运行时:
- 需要先运行写内存项目(会创建共享内存),之后在运行读内存项目(这样才能关联共享内存)
2. 线程
2.1 线程介绍
在Qt中提供了一个QThread类来描述线程,QThread提供了一个与平台无关的管理线程的方法,即一个QThread对象管理一个线程
2.2 为什么需要线程
原因一:进行耗时操作时,如果在UI线程(主线程) 里面进行耗时操作,界面不会响应用户操作,则会产生卡界面的现象,由于线程是异步的,则可以把这个耗时操作转移给子线程执行
原因二:为了提升性能,现在的电脑一般都是多核CPU,多线程的并行处理事务,将会大大提高程序性能
2.3 注意事项
- Qt的默认线程为UI线程(主线程)︰负责窗口事件处理或窗口控件数据的更新;
- 子线程负责后台的业务逻辑,子线程不能对窗口对象做任何操作,这些事需要交给UI线程;
- 主线程和子线程之间如果进行数据的传递,需要使用信号槽机制
2.4 四种使用方式
- 继承QThread类,重写run()方法,实例化QThread子类实例对象,调用start()
- 使用moveToThread将一个继承QObject的子类对象移至线程,内部槽函数均在线程中执行
- 使用QThreadPool,搭配QRunnable (线程池)
- 使用QtConcurrent(线程池)
2.5 继承QThread类 && 重写run函数
先创建一个自定义的类,然后让它继承自QThread
再在子线程中重写run函数,并将主线程中耗时的操作交个子线程中运行
之后就是在主线程中创建实例对象,并start开始线程
说明一下:
- MyThread* thread = new MyThread;只是实例化了一个对象,并没有创建子线程
- thread->start();这段代码将会新建一个线程,并调用里面重写的run函数
既然本质上还是要调用重写的run函数,为什么不直接调用run函数,而是用thread->start()?
-- 等价与:qt线程中Start()和run()的区别?
- start()会另开一个线程,去异步执行业务逻辑,run()只是一个普通的成员函数,它不会另开线程,只会在当前线程中去同步执行业务逻辑
运行程序
说明一下:
- 从输出结果来看,线程是异步执行的,而不是同步/顺序执行的
QThread实例存在于实例化它的旧线程中,而不是调用run()的新线程中。这意味着QThread的所有绑定的信号槽和调用方法都将在旧线程中执行。
说明一下:
- 由此可以证明,原来QThread实例化对象中所绑定的信号与槽,将只能在旧线程中调用
2.6 moveToThread函数 && 迁移子类对象到线程中
默认情况下我们在代码中创建的对象都属于主线程,这个对象的槽函数在调用的时候,占用的都是主线程的时间,
我们也可以将一个QObject类型的对象或子类对象通过moveToThread移动到子线程中去,这样当这个对象的槽函数被信号触发调用的时候,槽函数占用的就是子线程的时间。
更改此对象及其子对象的线程关联性。如果对象有父对象,则不能移动该对象。事件处理将在TargetThread中继续。
新建文件
代码编写
首先子定义的这个work是继承自QObject的,里面实现一个槽函数打印线程ID
而在Mainwindow2中,我们绑定信号与槽,并执行创建子线程,
注:我发起这个信号是通过ui界面中按钮的点击
运行程序:
-
此时因为worker对象属于当前主线程,因此它的槽函数是占主线程时间的,如果想让槽函数占子线程时间就可以使用moveToThread()将对象Worker移动到子线程中去
2.7 QThreadPool && QRunnable (线程池)
QThreadpool管理多个线程的集合。QThreadpool管理和回收单个QThread对象,以帮助降低使用线程的程序中的线程创建成本。
每个Qt应用程序都有一个全局QThreadpool对象,可以通过调用globallnstance()来访问。要使用QThreadpool线程之一,子类QRunnable并实现run()虚函数。然后创建该类的一个对象并将其传递给QThreadpool:start().
新建文件
编写代码
myrunnable.h
#ifndef MYRUNNABLE_H
#define MYRUNNABLE_H
#include <QRunnable>
/**
* @brief The MyRunnable class
* MyRunnable需要去继承QRunnable这个类,这个类就是去告诉线程池,我获取线程是要去做什么任务的
*/
class MyRunnable : public QRunnable
{
public:
MyRunnable();
void run();
};
#endif // MYRUNNABLE_H
myrunnable.cpp
#include "myrunnable.h"
#include <QDebug>
#include <QThread>
MyRunnable::MyRunnable()
{
}
// 子线程执行业务逻辑的
void MyRunnable::run()
{
qDebug() << "线程池的任务,执行业务逻辑 " <<QThread::currentThreadId();
}
mainwindow3.h
#ifndef MAINWINDOW3_H
#define MAINWINDOW3_H
#include <QMainWindow>
namespace Ui {
class MainWindow3;
}
class MainWindow3 : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow3(QWidget *parent = nullptr);
~MainWindow3();
private:
Ui::MainWindow3 *ui;
};
#endif // MAINWINDOW3_H
mainwindow3.cpp
#include "mainwindow3.h"
#include "ui_mainwindow3.h"
#include <QThreadPool>
#include "myrunnable.h"
#include <QDebug>
MainWindow3::MainWindow3(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow3)
{
ui->setupUi(this);
qDebug() << "主线程开始执行:" << QThread::currentThreadId();
// 线程的使用方式三: QThreadPool
// 1 实例化QThreadPool实例化对象
// 实例化QThreadPool方式有两种:
//QThreadPool *pool = new QThreadPool;
// 方式二: qt中会提供一个全局的线程池对象
QThreadPool *pool = QThreadPool::globalInstance();
//pool->setMaxThreadCount();
// 2 通过线程池执行任务
MyRunnable *task = new MyRunnable;
for(int i=0;i<10;i++)
{
// 从线程池中拿一个线程出来执行任务
pool->start(task);
}
qDebug() << "主线程执行结束:" << QThread::currentThreadId();
}
MainWindow3::~MainWindow3()
{
delete ui;
}
线程池对象创建方法:
- 方式一:new QThreadPool
- 方式二:QThreadPool::globalInstance(全局静态函数)
程序运行
说明一下:
- 为了更好的观察到现象,可以把任务数增多
- 此时就可以发现有相同的线程ID被重复使用了
2.8 [最简单]QtConcurrent(线程池)
通过QtConcurrent命名空间提供高级APl,可以在不使用互斥锁、读写锁、等待条件或信号量等低级线程原语的情况下编写多线程程序。使用QtConcurrent编写的程序会根据可用*处理器内核的数量自动调整所使用的线程数。
说简单点,就是我们使用QtConcurrent实现多线程时,可以不用考虑对共享数据的保护问题。而且,它可以根据CPU的能力,自动优化线程数。
和QThreadPool区别:
- QThreadPool自己计算和设置最佳的线程池个数,而QtConcurrent会自动去优化线程池个数
- QThreadPool是不能接收子线程返回结果的,但是QtConcurrent可以接收子线程执行结果的
使用时需要加入模板
```c++
QT += core gui concurrent
实际开发时,会遇到两种类型:
1. cpu密集型:做大量计算的,它的线程池个数=电脑核数
2. IO密集型:输入输出(数据库操作比较多的) ,线程池个数 = 核数*2
新建文件
代码编写
#include "mainwindow4.h"
#include "ui_mainwindow4.h"
#include <QDebug>
#include <QThread>
#include <QtConcurrent>
QString fun1()
{
qDebug() << "无参函数,线程执行任务" <<QThread::currentThreadId();
return "fun1..............";
}
QString fun2(QString name,int age)
{
qDebug() << "无参函数,线程执行任务name=" << name <<" age=" <<age <<QThread::currentThreadId();
return "fun2.................";
}
MainWindow4::MainWindow4(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow4)
{
ui->setupUi(this);
qDebug() << "主线程开始执行:" << QThread::currentThreadId();
// 线程使用方式四: QtConcurrent
// 将fun1和fun2任务加入到线程池中
QFuture<QString> f1 = QtConcurrent::run(fun1);
QFuture<QString> f2 = QtConcurrent::run(fun2,QString("zhangsan"),30);
// 执行任务,result()的返回值,就是fun1()和fun2()这两个函数所返回来的内容
f1.result();
f2.result();
qDebug() << "主线程执行结束:" << QThread::currentThreadId();
}
MainWindow4::~MainWindow4()
{
delete ui;
}
程序运行
通过观察发现它好像是同步的?
但其实是因为result执行线程任务,是需要等待并拿到返回结果的,所以看上去好像是同步执行
说明一下:
-
通过测试,上面的代码还是会在子线程任务之后执行,说明QtConcurrent还是异步操作
-
只是说如果在返回值之后做操作的话,必须要等返回值拿到结果以后才能执行
-
如果以后我的主线程耗费5s,子线程任务都分别要消耗5s
3.加锁
3.1 QMutex入门
- 这里创建了2个线程,一个线程对num循环5k次,由于没加锁导致结果不是1w
- 这里把锁加上就不会出现各个线程相互竞争的问题了
3.2 QMutexLocker自动解锁
- 因为上面的锁很容易忘记释放,忘记unlock,在逻辑复杂的情况下
- Qt中也有一个对互斥锁进行封装的类->QMutexLocker,类似与std::mutex智能指针
- C++11 也引入了std::lock_guard
3.3 其他补充说明
条件变量:QWaitCondition
信号量:QSemaphore
读写锁:QReadLocker、QWriteLocker、QReadWriteLock
-
这里就暂不介绍了,这里类和API用法和Linux中的类似,就是对系统函数/接口的封装,
-
要用到的时候,再查查文档
第九章 QSS
1. QSS
Qt中的QSS和前端中的CSS类似,也是通过设置样式美化界面
1.1 纯代码引入
1.2 qrc机制引入
1.3 ui界面中引入(推荐)
1.4 子控件->伪链接
查阅文档: Qt Style Sheets Reference
- 记不住就多查查
又比如:pressed虚类选择器
第十章. QT 中的HTTP 操作
1. api解读
1.1 QNetworkAccessManager Class 网络数据管理器
Header: | #include <QNetworkAccessManager> |
---|---|
qmake: | QT += network |
Since: | Qt 4.4 |
Inherits: | QObject |
- 使用时需要加上network这个模板
1.2 QNetworkReply Class 获取请求响应
Header: | #include <QNetworkReply> |
---|---|
qmake: | QT += network |
Since: | Qt 4.4 |
Inherits: | QIODevice |
- 使用时需要加上network这个模板
1.2 QNetworkRequestClass 发起请求类型
Header: | #include <QNetworkReply> |
---|---|
qmake: | QT += network |
Since: | Qt 4.4 |
Inherits: | QIODevice |
使用时需要加上network这个模板
2. 发起get请求
2.1 导入模块和头文件
2.2 创建请求对象并监听
- 处理函数需要自定义一下
2.3 构建请求并发送
#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
// 创建请求对象
QNetworkAccessManager* manager = new QNetworkAccessManager(this);
// 监听响应
connect(manager,&QNetworkAccessManager::finished,this,&MainWindow::replyHandle);
// 构建请求
QNetworkRequest request = QNetworkRequest(QUrl("http://117.72.104.209:8080/all_questions"));
// 发送请求
manager->get(request);// 会返回一个 QNetworkReply 对象,并在请求完成后自动触发 finished 信号。
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::replyHandle(QNetworkReply *reply)
{
QString content = reply->readAll();
qDebug() << content;
}
说明一下:
- 在上面代码中: manager->get(request);// 会返回一个 QNetworkReply 对象,并在请求完成后自动触发 finished 信号。
- 且这里是在url中传参是,get请求
3. 发起post请求 - json格式
传参我将使用最常见的json格式,不用想QT中肯定有对json字符串进行序列化和反序列化的函数,几乎每个语言都支持json的各种操作
3.1 导入json所需要的头文件
3.2 序列化
3.3 反序列化
3.3 mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
// 创建请求对象
QNetworkAccessManager* manager = new QNetworkAccessManager(this);
// 监听响应
connect(manager,&QNetworkAccessManager::finished,this,&MainWindow::replyHandle);
// 构建json->序列化
QJsonObject obj;
obj.insert("code","#include <iostream>\n#include <string>\n#include <vector>\n#include <map>\n#include <algorithm>\nusing namespace std;\nclass Solution{public:vector<int> twoSum(vector<int>& nums, int target){vector<int> ret{1,1};while(1);\n return ret;}};");
obj.insert("input","");
QJsonDocument doc(obj);
QByteArray data = doc.toJson(QJsonDocument::Compact); // 序列化成字节数组
// 构建请求
QNetworkRequest request = QNetworkRequest(QUrl("http://117.72.104.209:8080/jude/2"));
// 设置报头格式
request.setRawHeader("content-type","application/json");
// 发送请求
manager->post(request,data);// 会返回一个 QNetworkReply 对象,并在请求完成后自动触发 finished 信号。
}
MainWindow::~MainWindow()
{
delete ui;
}
// 打印json
void ShowJson(QJsonObject& result){
QString reason = result.value("reason").toString();
int status = result.value("status").toInt();
QString stderrMsg = result.value("stderr").toString();
QString stdoutMsg = result.value("_stdout").toString();
qDebug() << "reason:" << reason;
qDebug() << "status:" << status;
qDebug() << "stderr:" << stderrMsg;
qDebug() << "stdout:" << stdoutMsg;
}
void MainWindow::replyHandle(QNetworkReply *reply)
{
QByteArray content = reply->readAll();
QJsonParseError err;
// 反序列化
QJsonDocument doc = QJsonDocument::fromJson(content, &err);
// QJsonObject result = doc.object();
if (err.error != QJsonParseError::NoError) {
qDebug() << "JSON解析错误:" << err.errorString();
return;
}
if(doc.isArray()) {
QJsonArray array = doc.array();
for (const QJsonValue &value : array) {
if (value.isObject()) {
QJsonObject result = value.toObject();
ShowJson(result);// 打印操作
}
}
}
// 如果只是一个json则不需要循环了,直接拿数据
else if(doc.isObject()) {
QJsonObject result = doc.object();
ShowJson(result);//打印操作
}
reply->deleteLater();
}
说明一下:
-
设置报头格式:request.setRawHeader("content-type","application/json");
-
使用post请求方法:manager->post(request,data);
3.4 运行程序
再稍微改一下我们传入的json串,即在code对应的value中添加while(1)的死循环,如下:
第十二章 QML-初阶
0. 待优化(TODO)
1. 基础概念
QML 是 QT Meta-Object Language 的简称,是一个用于创建交互式用户界面的声明式语言。它最初由 Qt Company 开发,作为 Qt 应用程序框架的一部分。QML 是一种使用 JavaScript 和 JSON 语法的脚本语言,使得开发者能够轻松地构建跨平台的动态 UI
与以前在ui界面不同的是,用QML是一种纯代码的方式画客户端界面,它比ui界面拖控件更难,但又比qt designer中纯代码的方式简单
2. Qt QML与Qt widget区别
出现顺序:想都不用想肯定是Qt widget先出来(不然我们为什么先学QML)
性能比较 : 如果gpu性能越强则qml性能越好,反之Qt Widget更好
应用领域:
- widget主要集中在金融、军工、安防、航天、船舶、教育等领域
- 而qml主要集中在汽车仪表、车机、直播等领域
发展趋势: 对于发展趋势来说,在未来qml只会越来越流行
界面描述方式:
.ui
文件是基于 Qt Widgets,是 Qt Designer 生成的 XML 文件,通常使用Qt Designer来进行可视化编辑和设计,对于开发人员可以直观地拖放和配置界面组件.qml
文件是基于基于 Qt Quick,是 声明式语言 QML 写的界面文件,通常使用Qt Creator或其他文本编辑器进行编辑,开发人员需要手动编写QML代码来描述界面的结构、外观和行为(虽然也可以通过Qt Creator拖放和配置界面组件 但是不推荐)
QML适用于需要快速迭代、设计驱动和跨平台支持的应用程序。
Widgets更适合传统的桌面应用程序开发,对于较复杂的界面和交互需求,以及对细粒度控制更重要的场景,Widgets可能更有优势
3. Qt QML与Qt widget如何选择(面试题)
选择 QML 还是 Widgets,主要取决于项目的 UI 风格和开发需求:
说明一下:
- 如果项目追求现代、流畅、动画丰富的界面(比如移动端或嵌入式设备),我会选择 QML。它是声明式的,开发效率高,适合做炫酷动画、响应式界面
- 如果项目是传统的桌面软件,对稳定性和控件复杂度要求高,比如表格、树、菜单操作,我更倾向使用 Widgets。它成熟稳定,C++ 控制力强,性能好
- 如果项目需要兼顾两者,可以采用 QML + C++ 混合开发,用 QML 做界面、C++ 做逻辑或底层控制
所以我会根据目标平台、性能需求、团队熟悉程度综合判断,如果做跨平台、UI要求高的产品,我更倾向 QML;如果维护已有桌面工具类程序,Widgets 更合适
4. 认识Qt Quick
Qt Quick是Qt框架中用于创建现代化用户界面的模块。它基于Qt的核心功能,采用了优雅的声明性语言QML(Qt Meta-Object Language)和灵活的JavaScript编写界面的逻辑。Qt Quick提供了一套丰富的组件和可视化元素,使开发人员能够快速构建跨平台的动态、流畅和吸引人的用户界面
以下是关于Qt Quick的一些主要特点和功能:
- 使用了优雅的声明性语言QML(Qt Meta-Object Language)和灵活的JavaScript编写界面的逻辑
- Qt Quick中的可视化工具,能够通过拖放和配置的方式创建和编辑QML界面
- Qt Quick提供了丰富的预定义组件库,包括按钮、文本框、图像、滚动视图、列表视图等等
- Qt Quick具备强大的动画和转场效果支持
- Qt Quick可以轻松地在多个平台上运行,所以也具备跨平台性
Qt Quick提供了一种现代化、高效和灵活的方式来构建用户界面。它与Qt的其他模块(如Qt Widgets)结合使用,可以满足各种不同类型和规模的应用程序的需求。无论是创建桌面应用、移动应用还是嵌入式应用,Qt Quick都是一个强大而可选的选项
5. QML入门
5.1 新建项目
说明一下:
- 接下来就不断的下一步下一步就行了
5.2 项目解释
main.cpp
说明一下 :
- 跟其他程序一个也需要有一个入口函数main
main.qml
5.3 connect的第五个参数(面试题)
connect
的第五个参数是 连接类型(ConnectionType),用来指定信号和槽函数之间的调用方式,常见的有:
-
Qt::AutoConnection(默认):自动判断信号与槽,如果是同一个线程,就是直接调用;不同线程,就用队列调用(异步)。
-
Qt::DirectConnection:直接调用槽函数(同步执行)。
-
Qt::QueuedConnection:把槽函数放进事件队列(异步执行,常用于跨线程)。
-
Qt::BlockingQueuedConnection:像 Queued 一样,但会阻塞信号发送方,直到槽函数执行完(用于跨线程同步,慎用)。
-
Qt::UniqueConnection:防止重复连接同一对信号和槽
在多线程开发中,我会特别注意使用 Qt::QueuedConnection
来避免线程安全问题,保证信号槽在目标线程的事件循环中执行
5.4 数据类型
下面我就只列举一下类型,仅供参考
类型 | 描述 |
action | action类型拥有QAction的所有属性 |
enumeration | 一个枚举类型包含一组已命名的值 |
real | 一个real数字包含一个小数点,例如1.2或者-111.1 |
rect | 一个rect类型包含了x,y,width和height属性 |
size | 一个size类型包含了width和height属性 |
time | 一个time被指定为"hh:mm:ss" |
url | 一个URL是一个资源定位器,像一个文件名 |
variant | 一个variant类型是一个通用的属性类型 |
vector3d | 一个vector3d类型包含x,y和z属性 |
6. 常用控件
引入组件: import QtQuick.Controls 2.3
控件我们是学不完的,毕竟还有那么多属性,我的建议是用到的时候再去查,多用就记住了,我下面只是列举了一些常用的控件即属性
6.1 Text文本
6.2 Button按钮
6.3 Rectangle矩形
7. 布局
7.1 锚点布局
锚点布局是QML中最灵活的布局方式之一。通过定义控件的锚点(如顶部、底部、左侧、 右侧)与其他控件或父容器的关系,实现精确对齐
锚点属性:
-
anchors.left:左对齐。
-
anchors.right:右对齐。
-
anchors.top:顶部对齐。
-
anchors.bottom:底部对齐。
-
anchors.horizontalCenter:水平居中。
-
anchors.verticalCenter:垂直居中。
-
anchors.fill:填充整个父容器
边距和间距:
-
这个边距指定是外边距
-
anchors.leftMargin、anchors.rightMargin、anchors.topMargin、anchors.bottomMargin:设置锚点的边距。
-
anchors.margins:统一设置所有边距
基于父控件定位
基于父控件的水平,垂直居中
基于兄弟控件定位
7.2 行列布局
Row 布局用于将子元素水平排列。子元素会从左到右依次排列,直到填满父容器的宽度。
Column 布局用于将子元素垂直排列。子元素会从上到下依次排列,直到填满父容器的高度
一行 Row
一列Column
7.3 网格布局
7.4 浮动布局
Flow 布局是一种动态布局方式,用于将子元素按行或列排列,当空间不足时会自动换行
8. 信号与槽
8.1 控件信号
import QtQuick 2.12
// 引入控制组件
import QtQuick.Window 2.12
// 引入控制组件
import QtQuick.Controls 2.12
Window {
id:myWin
width: 350
height: 250
visible: true
title: qsTr("Hello World")
// 这个绑定值 等价于C++中的int cout = 1;
property int cout: 1
Button{
text: "点我"
x: 0
y: 30
onClicked:{
myWin.cout = 2 // 这里改变了count的值
}
}
// 当cout发生改变时,触发这个槽函数
onCoutChanged: {
// 控制台打印
console.log("count val changed!")
}
}
说明一下:
- 在QML中信号与槽,是不需要我们手动绑定的,自动就会绑定
- 内置的信号与槽的使用范围如上:
8.2 自定义信号
说明一下:
- 当然qml中的信号与槽也是可以传递参数的
- 自定义信号名首字母不能大写,对应的槽函数名 = on + 首字符大写 + 其他
9. 登录系统2.0(TODO)
这次我打算使用qml来重画一下前面那个系统,但由于时间匆忙,就画个主页面就完事了
第十三章 QML-进阶
1. qml导入javascript
1.1 新建js文件
1.2 引入并使用javascript
说明一下:
- 引入格式 import 文件路径 as 名字
- js代码中是以;结尾的
2.qml与c++互相操作
2.1 c++操作qml中元素,属性,函数
纯qml用id就行,但是要是用qml和c+混合开发提取属性时,就需要objectName 属性
ojectName
是 Qt C++ 中的 QObject 的属性,通常用于 C++ 中用 findChild
查找控件。但在 QML 中的大部分控件(如 Button
)默认并不继承 QObject 的这个属性,所以你不能直接用 objectName
import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 2.12
// 引入js文件
import "test_js.js" as Hello
//对象
Window {
//属性
id: myWIn
width: 640
height: 480
visible: true
title: qsTr("Hello World")
//子对象
Button{
id:mybtn
text: "按钮1"
objectName:"mybtn"
//槽函数
onClicked: {
console.log("mybtn clicked!");
}
//函数
function xxx(){
console.log("jfjfjjfjfjfjfjh")
}
}
}
2.2 qml操作c++的方法
原来我们可以通过js函数我们的业务逻辑操作.但是js功能有限(不能操作硬件),业务逻辑页写得不能太负责. 所以我们需要用c++写业务逻辑,通过qml来调用.
说明一下:
方法一:给qml使用的函数声明前,需要加上Q_INVOKABLE
方法二:使用public slots
来暴露方法
step1: 创建一个类型
step2: 注册类
参数说明:
-
uri
名("Myojbcet"
)可以随便起,但不能和系统模块重名。 -
version
是为了模块升级管理,如果你将来发布1.1
、2.0
,QML 可以通过版本号选择用哪个。 -
qmlName
是给 QML 创建对象用的名字(和类名可以不同)
step3: 以模块的方式导入类
step4: 通过类创建对象
step5 调用对象方法
第十四章 QT绘画
1. 常见方式
- QML Canvas: 2d
- QPainter: 2d
- qt chart : 统计图
- opengl/Vulkan/Quick 3D: 3d
2. 使用场景
-
常规界面开发:优先使用 QPainter/QML Canvas
-
高性能可视化:使用 OpenGL + Framebuffer Object 混合方案
-
混合使用场景:可通过 QOpenGLWidget 将OpenGL 内容嵌入 QWidget 体系
3. QWidget中的QPainter
画图相关的操作,一般不会放到QWidget的构造函数中调用执行,而是Qt提供了要给paintEvet事件处理函数,在这里进行调用,和它对应的是QPaintEvent事件
说明一下:
- QPainter: 提供一系列的绘画方法,实现绘画动作的
- QPaintDevice: 画板,其实QWidget也是QPaintDevice的子类
- QPen: 画笔
- QBrush: 画刷(填充时的属性)
4. QML Canvas: 2d
4.1 概念
Canvas
是QML中的一个绘图组件,允许开发者在界面上进行动态的绘制。它提供了一种底层的方式,通过 JavaScript 来绘制图形和图像。Canvas
是一个非常强大的组件,常用于实现自定义图形、动画、游戏以及图表绘制等场景
4.2 重要属性
Canvas
提供了多个重要的属性来控制绘图区域及其行为:
-
width
: 控制画布的宽度。 -
height
: 控制画布的高度。 -
contextType
: 设置上下文类型,可以选择"2d"
来启用二维绘图上下文。 -
antialiasing
: 是否启用抗锯齿,使绘制的图形更加平滑。类型为布尔值(true
或false
)。 -
smooth
: 是否启用平滑处理,主要影响图像缩放的效果。类型为布尔值(true
或false
)。 -
fillStyle
: 设置或获取当前填充颜色或样式。 -
strokeStyle
: 设置或获取当前描边颜色或样式。 -
lineWidth
: 控制线条宽度,影响绘制的线条。 -
lineCap
: 设置线条的端点样式,可能的值包括"butt"
、"round"
和"square"
。 -
lineJoin
: 设置线条连接处的样式,可能的值有"bevel"
、"round"
和"miter"
。 -
globalAlpha
: 设置全局透明度,用于控制所有绘图操作的透明度
4.3 重要函数
Canvas
提供了多个方法用于执行绘制操作:
-
getContext(type)
: 获取绘图上下文,通常用于获取"2d"
上下文来进行绘图操作。 -
clear()
: 清除画布上的所有内容,恢复为空白状态。 -
beginPath()
: 开始一个新的路径,在绘制形状之前调用此方法。 -
closePath()
: 结束当前路径,可以用来闭合路径。 -
moveTo(x, y)
: 移动到指定的坐标(x, y)
,此方法会设置起始点。 -
lineTo(x, y)
: 从当前位置绘制一条直线到(x, y)
。 -
arc(x, y, radius, startAngle, endAngle, anticlockwise)
: 绘制圆弧或弯曲路径。传入圆心(x, y)
、半径、起始角度、结束角度以及是否逆时针绘制。 -
fill()
: 填充当前路径,使用当前的fillStyle
属性。 -
stroke()
: 描边当前路径,使用当前的strokeStyle
属性。 -
save()
: 保存当前绘图状态。 -
restore()
: 恢复到最近保存的绘图状态。 -
rotate(angle)
: 旋转画布,参数是旋转的角度(弧度)。 -
translate(x, y)
: 平移画布,参数是平移的距离(x, y)
。
4.4 重要信号
Canvas
也提供了一些常用信号,帮助在绘制过程中进行交互或响应某些事件:
-
onPaint
: 当需要重新绘制时触发。通常用来告诉应用何时重新绘制画布内容。 -
onResize
: 当width
或height
属性改变时触发。可以用于响应画布尺寸的变化。
4.5 常用枚举
Canvas
使用一些常用的枚举类型来配置不同的绘图行为,常见的枚举包括:
-
Canvas.LineCap
: 用于设置线条端点的样式。可选值:-
Canvas.Flat
: 平的线条端点。 -
Canvas.Round
: 圆形线条端点。 -
Canvas.Square
: 方形线条端点。
-
-
Canvas.LineJoin
: 用于设置线条连接的样式。可选值:-
Canvas.Bevel
: 斜角连接。 -
Canvas.Round
: 圆形连接。 -
Canvas.Miter
: 斜接连接。
-
-
Canvas.TextAlign
: 用于设置文本对齐方式。可选值:-
Canvas.Start
: 向起始位置对齐(默认)。 -
Canvas.Center
: 居中对齐。 -
Canvas.End
: 向结束位置对齐。
-
-
Canvas.TextBaseline
: 用于设置文本基线对齐方式。可选值:-
Canvas.Alphabetic
: 字母基线。 -
Canvas.Middle
: 中间对齐基线。 -
Canvas.Top
: 顶部基线
-
4.6 入门案例
import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 2.12
Window {
visible: true
width: 600
height: 400
Canvas {
id: canvas
width: parent.width
height: parent.height
contextType: "2d"
// 属性设置
antialiasing: true
smooth: true
// 在画布上绘制内容
onPaint: {
var ctx = canvas.getContext("2d");
ctx.clearRect(0, 0, canvas.width, canvas.height); // 清空画布
ctx.fillStyle = "lightblue";
ctx.fillRect(50, 50, 150, 100); // 绘制矩形
// 绘制圆形
ctx.beginPath();
ctx.arc(300, 150, 80, 0, Math.PI * 2);
ctx.fillStyle = "orange";
ctx.fill();
// 绘制文字
ctx.font = "24px Arial";
ctx.fillStyle = "black";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillText("Hello, Canvas!", 300, 250);
// 绘制线条
ctx.beginPath();
ctx.moveTo(100, 300);
ctx.lineTo(500, 300);
ctx.strokeStyle = "green";
ctx.lineWidth = 5;
ctx.stroke();
}
}
}
说明一下:
- 其实用qml中的canvas非常麻烦,这里不是难
- 而是需要调用一大堆函数,而且无法实时预览,反正我用时候相当难受
4.7 案例-车载仪表盘(TODO)
效果图
说明一下:
- 这个效果看着简单,其实很麻烦,那些刻度都是需要通过三角函数进行运算得到
- 还有这个指令肯定是移动,那怎么移动呢?
5. qt chart : 统计图
5.1 概念
Qt 是一个跨平台的应用程序开发框架,而 Qt Charts 是其中的一个模块。我记得它主要用于数据可视化,生成各种图表。具体来说,可能用于显示折线图、柱状图、饼图之类的
5.2 使用领域
工业监控,金融分析,科学实验,商业智能 ,医疗健康 ,嵌入式系统
6. opengl : 3D
6.1 概念
Qt 是一个跨平台的应用程序开发框架,主要用于图形用户界面(GUI)的开发,但也支持其他功能如网络、数据库等。 OpenGL 则是一个跨语言、跨平台的图形渲染 API,广泛用于 2D 和3D 图形处理。
结合 Qt 和OpenGL 时,Qt 提供了方便的窗口系统和 GUI 组件,而 OpenGL 处理高性能的图形渲染。这样的组合适合需要复杂图形界面同时又有高质量图形渲染需求的应用
6.2 使用场景
可能的场景包括:
- 3D 建模或 CAD 软件,需要交互式界面和实时渲染;
- 游戏开发,尤其是需要定制 UI 的游戏;
- 科学可视化,如医学成像或地理数据展示;
- 虚拟现实(VR)或增强现实(AR)应用,需要低延迟和高性能图形;
- 教育软件,用于演示物理模拟或数学图形;
- 工业设计工具,比如机械仿真。此外,Qt 的跨平台特性让这些应用可以在不同操作系统上运行,而 OpenGL 确保图形性能一致。