1.QT概述
Qt是一个跨平台的C++图形用户界面应用程序框架
Qt技术用于进行“客户端开发”,特指桌面应用开发,无法开发网页前端,也不能开发移动应用(Qt官方支持移动应用开发,但没有知名商用移动应用是Qt开发的)
客户端:直接和用户端交互的程序,其主要任务是编写和用户交互的界面。
1.1 用户交互界面的两种典型风格
- 命令行界面/终端界面(TUI),多见于给程序员使用的专业软件
- 图形化界面(GUI),多见于给普通用户使用的软件,Qt就是用于编写桌面的GUI程序的一套框架
1.2 Windows下开发GUI的常用方案
- Windows API, windows系统提供原生API,开发起来比较原始,也很繁琐
- MFC,上世纪90年代
- Qt,1991年左右(MFC早就不更新了,Qt仍然在推陈出新,更新新的版本),支持跨平台,支持Windows、Linux、Mac以及嵌入式系统,其中Linux主要是给服务器使用,服务器不需要图形界面
- Windows Forms,给C#量身定做的一套开发GUI技术体系,C#是微软自己搞的一套编程语言
1.3 什么是框架
框架:本质是一群大佬发明出来,目的是提高程序员开发效率,以及减少出错概率。通常代码越自由、灵活,越容易出错,因此框架通过限制程序员自由,从而减少出错,程序员配合框架,完善填充框架中留出的细节。
库:被程序员调用的。
1.4 Qt版本以及优缺点
Qt最新的版本是Qt6,相对Qt5来说核心功能区别不大,开发中并非越新越好,商业开发时,引入新的特性,不如先确保不引入严重的问题。
Qt优点:
- 跨平台,几乎支持所有的平台
- 接口简单,容易上手,学习QT框架对学习其他框架有参考意义
- 一定程度上简化了内存回收机制(半自动的垃圾回收,能够简化内存释放,也能够尽可能小的影响程序的运行效率)
- 开发效率高,能够快速的构建应用程序
- 有很好的社区氛围,市场份额在缓慢上升
- 可以进行嵌入式开发
2.环境搭建
Qt开发环境,需要安装3个部分
- C++编译器,gcc、cl.exe(不是Visual Studio,编译器!=IDE,编译器只是IDE调用的一个程序)。当安装QT creator时,在选择组件时需要勾选上MingGWXXX 64 bit,注意不是MSVCXXX 64 bit(使用VS开发时勾选这个)
- Qt SDK,软件开发工具包,windows版本Qt SDK里已经内置了C++的编译器,内置的编译器是mingw,windows版本的gcc/g++,如果要用VS内置的cl.exe作为编译器也可以,但需要配置很多额外的东西,容易出错。QT的具体安装过程中,需要把对应的c++编译器一起勾选。
- 需要有一个QT的集成开发环境(IDE)
- Qt官方提供的Qt Creator(最容易入门,最容易上手的方式,开箱即用,不需要任何额外配置,也存在不少bug,有些bug影响使用体验,但整体来说使用方便)。
- VisualStudio功能更强, 但需要额外配置,更容易出错,需要给vs安装qt插件,并且需要把Qt SDK使用VS编译器重新编译。
- Eclipse,本身是个IDE平台,可以搭配不同的插件构成不同的IDE。
说是安装三个,其实只需要安装一个Qt SDK,另外两个也就有了。
2.1 QT SDK中常用工具
- 官方文档。安装完后,开始菜单中找到QT,可以看到第一个为QT助手,这个即为离线版本的官方文档
- QT Designer,图形化的设计界面的工具,通过拖拽控件来快速生成界面,后续通常搭配QT Creator使用
- Linguist,QT语言家,作用是对国际化进行支持,允许单独创建一个语言配置文件,把界面上需要用到的各种文字,都配置到文件中,并且在文件中提前把各种语言翻译都配置进去,就可以起到“一键式切换语言”效果。
- QT Creator。Qt的集成开发工具(学习QT主要使用的工具)
3. 项目创建
创建时,会出现下图,项目中通常选择Application(应用程序),选择后出现的第二列中需要知道的含义如下:
- Widgets Application:图形化界面应用程序
- Console Application:控制台应用程序
- for Python:QT不仅仅支持C++,也支持Python和java
- Quick:是QT新出的用于开发GUI的方式
选择后会出现下图所示界面,填入项目名和路径即可
下一步,出现的构建系统,直接选qmake即可,是通过QT写的程序,涉及到一些列的元编程技术,通过代码来生成代码。QT框架会在编译时,自动先调用一系列的生成工具,基于自己写的代码,生成一系列其他的C++代码,最终编译的代码,也是最后生成的这些代码。QT提供了三种构建工具: - qmake,老牌QT构建工具
- cmake,并非QT专属,很多开源项目都会用cmake
- qbs,新一代的QT构建工具
下一步,使用QT创建项目会自动生成一些代码,生成的代码就包含一个类,这里就是要选择这个自动生成的类的父类是谁: - QMainWindow:完整的应用程序窗口,可以包含菜单栏、工具栏、状态栏等
- QWidget:表示一个控件,窗口上的一个具体元素,如输入框、按钮、下拉框、单选按钮、复选按钮
- QDialog:表示一个对话框
QT种内置的类都是以“Q”开头。这里生成的文件名和类名是关联的,这样的关联并非强制。
图中的Generate form选项,QT创建图形化界面程序有两种方式: - 直接通过C++代码创建界面
- 通过form file,以图形化方式生成界面,这时可以使用QT Designer来编辑ui文件,从而以图形化方式快速方便的生成图形界面
下一步,翻译文件选择,直接下一步即可
下一步,选择基于哪个编译器的QT,MingGW是QT内置的,MSVC是VisualStudio中的编译器
结果,注意项目目录不能带有中文,否则可能报错
3.1 代码解释
项目创建后,main.cpp内容如下:
main的形参就是命令行参数:参数个数、参数内容列表
#include "mywidget.h"
#include <QApplication>
int main(int argc, char *argv[])//main的形参就是命令行参数
{
QApplication a(argc, argv);//编写一个QT图形化界面程序,一定要有QApplication对象
MyWidget w;//这里的MyWidget就是在创建项目时,填写的生成的类名对应的子类,此处父类就是QWidget
w.show();//让控件显示出来,如果用hide则可以让控件隐藏
return a.exec();//linux中也学过6个函数,叫做exec,进程程序替换,把可执行文件中的代码和数据,替换到当前进程中。这里QT中的exec与linux中的exec没有任何关系,只是名字恰好一样
}
头文件MyWidget.h内容如下:
#ifndef MYWIDGET_H//这里是Widget类的声明,同时保证头文件只被包含一次
#define MYWIDGET_H
#include <QWidget>
QT_BEGIN_NAMESPACE
namespace Ui { class MyWidget; }
QT_END_NAMESPACE
class MyWidget : public QWidget//继承自QWidget,想使用这个类,就要包含对应的头文件,QT中内置类需要包含的头文件的名字和类名一致,也不是所有QT类都需要显示包含,可能间接包含
{
Q_OBJECT//QT内置的宏,宏本质是文本替换,展开后会生成一大堆代码,当需要引入“信号”和“槽”,就需要引入这个宏
public:
MyWidget(QWidget *parent = nullptr);//构造,QT中引入了对象树机制,即创建的QT对象,就可以将这个对象挂到对象树上,往树上挂就需要指定“父节点”,一个节点可以有N个子,只能有1个父,对象树就是一个普通的N叉树。
~MyWidget();//析构
private:
Ui::MyWidget *ui;//与form file密切相关
};
#endif // MYWIDGET_H
MyWidget.cpp内容如下
#include "mywidget.h"//创建项目生成的头文件
#include "ui_mywidget.h"//form file被qmake生成的头文件
MyWidget::MyWidget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::MyWidget)
{
ui->setupUi(this);//将form file生成的界面与当前widget关联起来
}
MyWidget::~MyWidget()
{
delete ui;
}
form file,当直接双击ui文件,会进入Qt Designer,打开ui文件
上图中,左边一列是Qt内置的控件,拖拽控件到程序窗口中可以创建具体界面。右边是编辑属性,每个控件都有很多属性,影响控件具体的行为
此时点击QT左侧的编辑,会看到ui文件的本来面貌,格式为xml,与html类似,都使用成对的标签表示数据,内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MyWidget</class>
<widget class="QWidget" name="MyWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>333</width>
<height>252</height>
</rect>
</property>
<property name="windowTitle">
<string>MyWidget</string>
</property>
</widget>
<resources/>//单标签
<connections/>
</ui>
QT中使用xml文件就是描述程序的界面是什么样的,进一步的qmake会调用相关工具,依据xml文件生成C++代码,把完整界面构造出来
Empty.pro文件,是qmake工具构建时的重要依据,内容如下:
QT += core gui//要引入的qt模块,这里目前引入的是core和gui
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
CONFIG += c++11//c++版本
# The following define makes your compiler emit warnings if you use
# any Qt feature that has been marked deprecated (the exact warnings
# depend on your compiler). Please consult the documentation of the
# deprecated API in order to know how to port your code away from it.
DEFINES += QT_DEPRECATED_WARNINGS
# You can also make your code fail to compile if it uses deprecated APIs.
# In order to do so, uncomment the following line.
# You can also select to disable deprecated APIs only up to a certain version of Qt.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
//sources、headers、forms描述了当前项目中,参与构建的文件有哪些,这个地方不需要手动修改,qt会自动维护
SOURCES += \
main.cpp \
mywidget.cpp
HEADERS += \
mywidget.h
FORMS += \
mywidget.ui
# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target
.pro文件类似于之前Linux中学习的makefile文件,qmake搭配.pro起到的作用与makefile类似
如果编译运行QT项目,构建过程中还会生成一些中间文件,首先找到pro文件所在目录
上图所示就是项目文件,向上一级结果如下
如上图,运行一次程序后,会在项目目录并列的地方,多出来一个"build-XXXXXX"目录,这个目录里面就是该项目运行过程中产生的一些临时文件,如下:
上图中,makefile解释:编译QT程序,仍会用到Makefile,只不过不需要手动写,而是qmake自动生成。
此外,ui_xxxx.h文件是由前文所述的xml文件生成的,且查看MyWidget.cpp内容也可看到,包含了这个自动生成的.h文件,如下是MyWidget.cpp文件内容:
自动生成的.h文件内容如下,这个Ui_MyWidget对应的就是代码中的MyWidget,MyWidget.cpp中调用的setup函数就是下图中的setup函数,会进行具体的界面创建操作:
编译完成的exe文件可在debug/release目录下找到
3.2 图形化方法创建helloworld
QtDesigner右上角,通过树形结构,显示当前界面有哪些控件。
可以通过向界面拖拽一个QLabel控件,此时,ui文件的xml中会多出一段代码:
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MyWidget</class>
<widget class="QWidget" name="MyWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>333</width>
<height>252</height>
</rect>
</property>
<property name="windowTitle">
<string>MyWidget</string>
</property>
<widget class="QLabel" name="label">
<property name="geometry">
<rect>
<x>100</x>
<y>80</y>
<width>101</width>
<height>16</height>
</rect>
</property>
<property name="text">
<string>hello world</string>//这里就是多出的代码
</property>
</widget>
</widget>
<resources/>
<connections/>
</ui>
ctrl+R编译运行结果如下:
前文所述的ui_widget.h中也可以看到创建这个helloworld的操作:
除了通过拖拽直接生成,也可以通过代码来生成helloworld,一般通过代码构造界面时,会将构造界面的代码放到Widget/MainWindow的构造函数中
4.常用的控件
创建控件时,可以在堆上创建,也可以在栈上创建
4.1 Label
标签,界面上用于显示字符串的控件
QLabel * label = new QLabel(this);//这里的this指定了父对象,确定好Label在对象树中的位置
label->setText("hello world");//设置好要显示的字符串
上述代码创建了一个hello world字符串,默认位置在左上角
属性:
- 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最多只是两个方向有效
- openExternalLinks:是否允许打开一个外部的链接
- buddy:给QLabel关联一个伙伴,点击QLabel时会激活对应伙伴,若伙伴是个QCheckBox,则该QCheckBox会被选中
几种文本类型的实例:
//QLabel
QLabel *label1 = new QLabel(this);
QLabel *label2 = new QLabel(this);
QLabel *label3 = new QLabel(this);
label1->setTextFormat(Qt::PlainText);//纯文本
label1->setText("aaa");
label2->setTextFormat(Qt::RichText);//富文本
label2->setText("<b>这是一段富文本</b>");//加粗
label3->setTextFormat(Qt::MarkdownText);//markdown
label3->setText("# 标题");
label1->move(0, 10);
label2->move(100, 10);
label3->move(200, 10);
上述代码创建了三种label,分别对其中的文本类型进行设置,效果如下:
Label设置图片以及令其填满整个窗口的方法如下:
//QLabel设置图片
QLabel *label = new QLabel(this);
label->setPixmap(QPixmap(":/Pic/img2.jpg"));
label->setScaledContents(true);//令其自动拉伸,填充满整个窗口
4.1.1 对象树
在堆上创建label后,即使不手动delete,也不会产生内存泄漏,label对象会在合适的时候被析构释放,之所以能释放,是因为把这个对象挂在对象树上。通过树形结构,可以将界面上显示的控件对象组织起来,从而在合适的时机统一释放,合适的时机就是窗口被关闭时。
如果在栈上创建控件,可能会存在提前释放的问题,比如将前文代码创建label的代码替换为以下代码:
QLabel label(this);
label.setText("hello world");
此时运行后看不到hello world,这是因为在栈上创建控件,导致构造函数运行结束后对象被直接释放了
可以自行实现一个自定义label类,.h文件如下:
#ifndef MYLABEL_H
#define MYLABEL_H
#include <QLabel>
#include <iostream>
using namespace std;
class MyLabel : public QLabel
{
public:
//构造函数使用带QWidget*版本的,这样才能确保自己的对象能够添加到对象树
MyLabel(QWidget *parent);
//创建自定义的类,主要目的是自定义一个析构函数,自动完成析构操作
~MyLabel();
};
#endif // MYLABEL_H
.cpp内容如下:
#include "mylabel.h"
MyLabel::MyLabel(QWidget *parent):QLabel(parent)//只有调用父类构造,才能让自己的类对象加入到QT对象树中
{
}
MyLabel::~MyLabel()
{
cout<<"mylabel is deleted"<<endl;
}
需要注意自行实现控件时,为了添加到对象树,需要在构造时自动调用父类的构造。此外,这里的代码为了查看对象树的自动释放效果,在析构中添加了打印语句,当运行后关闭显示的窗口,会发现该语句被打印,验证了对象树在窗口被关闭时自动释放控件对象。
4.1.2 打印时的乱码问题
QT中当打印中文时,会发现乱码。
目前表示汉字字符集主要通过两种方式
- GBK,使用2字节表示一个汉字,windows简体中文版默认字符集就是GBK
- UTF8,变长编码,表示一个符号,使用的字节数有变化,2-4,但是utf8中,一个汉字一般是3字节,Linux默认就是utf8
乱码问题出现的原因有且只有一个:编码方式不匹配,如果字符串本身是utf8,但终端按照gbk方式解析,此时就会乱码(拿着utf8的数值,查询gbk的码表)
可以对代码文件另存为,查看弹窗显示的编码:
QT Creator内置的终端不确定是不是utf8,这个终端好像不能设置字符串编码,这里出现乱码就不是utf8了
QString可以自动处理编码方式,打印时使用QDebug()进行打印,需要包含头文件
qDebug()<<"hello被销毁";
后续在QT中,如果想通过打印日志的方式输出调试信息,优先使用qDebug,使用cout也可,但cout对于编码处理不好,在windows上容易乱码,如果是linux使用qt一般没事,linux默认编码是utf8
使用qDebug()打印的调试日志,是可以统一进行关闭的
输出的日志,是开发阶段,调试时使用的,如果程序发给用户,不希望用户看到这些日志,qDebug可以通过编译开关,实现一键式关闭
4.1.3 事件
当我们给label设置图片后,通过拖拽窗口想改变图片大小,此时发现窗口大小改变,但图片大小并未改变:
当拖拽时,会触发事件,这里触发的是resize事件(resizeEvent),这种事件是连续变化的,把窗口尺寸从A拖拽到B的这个过程,会触发一系列resizeEvent,此时可以借助resizeEvent来完成上述功能,可以让widget窗口类,重写父类的resizeEvent虚函数,在鼠标拖动窗口尺寸的过程中,这个函数会被反复调用执行,每次触发一个resizeEvent事件都会调用一次对应的虚函数。
如果希望图片可以跟随拖拽窗口的大小发生改变,则需要重新resizeEvent事件
4.1.4 设置文字对齐方式
对于显示文字的label,可以设置其中的文字对齐方式,如水平居中,垂直居中,靠右靠左等,示例如下
//QLabel设置文本对齐
ui->LabelAlignment->setText("这是一段文本");
ui->LabelAlignment->setAlignment(Qt::AlignHCenter);//水平居中
ui->LabelAlignment->setAlignment(Qt::AlignVCenter);//垂直居中
ui->LabelAlignment->setAlignment(Qt::AlignVCenter | Qt::AlignRight);//垂直居中
如上图所示,红色框是label的范围,可见最后将文本设置为垂直居中且靠右
4.1.6 设置自动换行、缩进、边距
有时文本数量过多,需要设置文本自动换行:
//QLabel设置文本换行、缩进、边距
ui->LabelAlignment->setText("这是一段很长的文本这是一段很长的文本这是一段很长的文本这是一段很长的文本这是一段很长的文本这是一段很长的文本这是一段很长的文本");
ui->LabelAlignment->setWordWrap(true);//设置自动换行
ui->LabelAlignment->setIndent(50);//缩进50像素
ui->LabelAlignment->setMargin(10);//设置边距
4.1.7 设置伙伴
QLabel中写的文本,可以指定快捷键,是在文本中使用&跟上一个字符来表示快捷键。比如&A通过键盘上的alt+a来触发这个快捷键,&B通过键盘上的alt+b来触发,绑定伙伴关系后,通过快捷键可以选中对应的单选按钮/复选按钮
//QLabel设置伙伴
QRadioButton *b1 = new QRadioButton(this);
QRadioButton *b2 = new QRadioButton(this);
b1->setText("&c");//1.注意这里设置快捷键
b2->setText("&d");
QLabel *label1 = new QLabel(this);
QLabel *label2 = new QLabel(this);
label1->setText("快捷键1");
label2->setText("快捷键2");
b1->move(50, 50);
b2->move(50, 100);
label1->move(100, 50);
label2->move(100, 100);
label1->setBuddy(b1);//2.绑定伙伴
label2->setBuddy(b2);
4.2 QLineEdit
使用代码创建QLineEdit的方式如下
//创建QLineEdit
QLineEdit *lineEdit = new QLineEdit(this);
lineEdit->setText("hello qt");
效果如下:
核心属性:
- text:输入框中的文本
- inputMask:输入内容格式约束
- maxLength:最大长度
- frame:是否添加边框
- echoMode:显示方式,默认为QLineEdit::Normal,输入什么就会显示什么;也可以设置为QLineEdit::Password,这种模式下输入的字符会被隐藏,通常用*或者=代替;还有QLineEdit::NoEcho,这种模式下,文本框不会显示任何字符。
- cursorPosition:光标所在位置
- alignment:文字对齐方式,设置水平和垂直的对齐
- dragEnabled:是否允许拖拽
- readOnly:是否是只读的(不允许修改)
- placeHolderText:当输入框内容为空时,提示什么信息
- clearButtonEnabled:如果输入框为空,此时没有任何显示,一旦用户在输入框中输入内容,右侧会出现一个清空按钮,一旦点击,会清除掉输入框中的全部内容
核心信号 - cursorPositionChanged(int old, int new):鼠标移动时发出信号,old为之前的位置,new为新位置
- editingFinished:当返回或回车时,或者行编辑失去焦点时,发出此信号
- returnPressed:当返回或回车键按下时发出信号,如果设置了验证器,必须要验证通过,才能触发
- selectionChanged:当选中的文本改变时,发出此信号
- textChanged:当QLineEdit中的文本改变时,发出此信号,text是新的文本,代码对文本的修改能够触发这个信号
- textEdited:当QLineEdit中的文本改变时,发出此信号,text是新的文本,代码对文本的修改不能触发这个信号
使用示例如下,实现通过按钮点击获取输入框中的内容
//QLineEdit 让用户输入个人信息,通过提交按钮获取到输入的内容
QLineEdit *edit = new QLineEdit(this);
edit->setClearButtonEnabled(true);//当输入时,右侧出现清空按钮
edit->setEchoMode(QLineEdit::Password);//设置输入模式为密码
QPushButton *button = new QPushButton(this);//提交按钮
edit->setPlaceholderText("请输入用户名");
button->setText("提交");
button->move(50, 50);
connect(button, &QPushButton::clicked, this, [=](){
QString name = edit->text();
qDebug()<<name;
});
});
有时需要输入手机号,需要验证一下手机号是否合规。
下面的代码实现对输入内容的验证,查看是否是合法的输入,这里要求输入必须以1开头,后续紧跟连续10个字符,按照上述代码设置lineEdit后,可以保证输入控制在11个以内,后续再输入是不会显示的
//正则表达式,给单行输入框设置验证器,基于正则表达式完成验证
QRegExp regExp("^1\\d{10}$");//验证手机号码的正则表达式,以1开头,紧跟10的数字,$表示结尾
QLineEdit *edit = new QLineEdit(this);
edit->setValidator(new QRegExpValidator(regExp));//后续可以使用这个验证其来查看输入的合法性
connect(edit, &QLineEdit::textEdited, this, [=](const QString &text){
QString content = text;
int pos = 0;//记录如果不符合要求,则输出是从哪个位置开始不符合要求
if(edit->validator()->validate(content, pos)==QValidator::Acceptable){
qDebug()<<"可用";
}
else{
qDebug()<<"不可用";
}
});
实现判断两次输入的密码是否一致
//输入两次密码是否一致
QLineEdit *edit1 = new QLineEdit(this);//设置输入框
QLineEdit *edit2 = new QLineEdit(this);
edit1->setEchoMode(QLineEdit::Password);
edit2->setEchoMode(QLineEdit::Password);
edit2->move(0, 50);
connect(edit2, &QLineEdit::textEdited, this, [=](const QString &text){
if(edit1->text().isEmpty() && edit2->text().isEmpty()){
qDebug()<<"密码不可为空";
return;
}
if(edit1->text()==edit2->text()){
qDebug()<<"密码相同";
}
else{
qDebug()<<"密码不一致";
}
});
上述代码通过监听textEdited信号,实时判断输入的内容是否相同
4.3 QTextEdit
多行输入框
//创建QTextEdit
QTextEdit *textEdit = new QTextEdit(this);
textEdit->setText("aaa");
属性:
- 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:总是关闭滚动条
- horizontalScrollBarPolicy:水平方向滚动条的出现策略,Qt::ScrollBarAsNeeded根据内容自动决定是否需要滚动条,是默认值;Qt::ScrollBarAlwaysOff总是关闭滚动条;Qt::ScrollBarAlwaysOn总是显示滚动条
核心信号: - textChanged():文本内容改变时
- selectionChanged():选中范围改变时
- cursorPositionChanged():光标移动时
- undoAvailable(bool):可以进行undo操作时
- redoAvailable(bool):可以进行redo操作时
- copyAvailable(bool):文本被选中/取消时
//QTextEdit
QTextEdit *edit = new QTextEdit(this);
edit->move(100, 100);
connect(edit, &QTextEdit::textChanged, this, [=](){//当内容有变化时触发
QString text = edit->toPlainText();//获取文本
qDebug()<<text<<endl;
});
connect(edit, &QTextEdit::selectionChanged, this, [=](){//选择范围改变时触发
QTextCursor cursor = edit->textCursor();
qDebug()<<cursor.selectedText();//获取选中的文本
});
connect(edit, &QTextEdit::cursorPositionChanged, this, [=](){//光标位置改变时触发
QTextCursor cursor = edit->textCursor();
qDebug()<<cursor.position();//获取光标的位置
});
connect(edit, &QTextEdit::undoAvailable, this, [=](bool state){//光标位置改变时触发
qDebug()<<"undo:"<<state;//获取状态
});
connect(edit, &QTextEdit::redoAvailable, this, [=](bool state){//光标位置改变时触发
qDebug()<<"redo:"<<state;//获取状态
});
上述代码展示了QTextEdit中的各个信号使用,需要说明的时最的undo和redo,正常编辑时是undo,当按下ctrl+z时会触发redo,重新输入
4.4 QPushButton
继承自QAbstractButton,这个类是个抽象类,是其他按钮的父类
属性:
- text:按钮中的文本
- icon:按钮中的图标
- iconSize:按钮中图标的尺寸
- shortCut:按钮对应的快捷键
- autoRepeat:按钮是否会重复触发,当左键按住不放时,如果设置为true,会持续产生鼠标点击事件,如果设置为false,必须释放鼠标,再次按下鼠标时,参能产生点击事件(相当于游戏手柄的连发效果)
- autoRepeatDelay:重复触发的延时事件,按住按钮多久后,开始重复触发
- autoRepeatInterval:重复触发的周期
按钮使用实例如下:
//创建按钮,QPushButton
QPushButton *pushButton = new QPushButton(this);
pushButton->setText("按钮");
此时按钮点击后暂时没有反应,需要通过信号和槽定义定义控件行为
4.4.1 connect函数
connect主要有4个参数:
- 信号发送者,Qt Designer拖拽创建控件时,会自动给控件分配obejctName属性,这个属性值要求必须是唯一的,qmake处理ui文件时,会根据这里的obejctName生成对应C++代码,代码中QPushButton对象的变量名就是这里的objectName,这个变量就是ui属性中的成员变量
- 函数指针,当触发控件时,需要发出的信号
- 接收信号者
- 接收信号者要进行的操作
调用connect的代码如下:
//创建按钮,QPushButton
QPushButton *pushButton = new QPushButton(this);
pushButton->setText("按钮");
//添加点击操作
connect(pushButton, &QPushButton::clicked, this, &MyWidget::handleClick)//这里因为是用代码创建控件,所以直接传入变量名作为第一个参数即可,如果是通过拖拽的方式,需要传入的是ui->objectName(这个name需要自行查看,可以自定义修改),handleClick是需要自行实现的函数名
当使用拖拽方式创建控件时,其已经作为ui对象的成员变量了,不需要作为Widget的成员,这也是为什么拖拽创建控件时,connect的第一个参数为ui->ObjectName
connect是QObject提供的静态的成员函数,QT中提供的这些类,本身存在一定的继承关系。
QObject是其他QT内置类的顶层类
QWidget直接继承自QObject,其他控件继承自QWidget
函数原型:
connect(
const QObject *sender,//描述信号是哪个控件发送的
const char *signal,//信号的类型
const QObject *receiver,//哪个对象负责处理
const char *method,//描述对象如何处理信号
Qt::ConnectionType type = Qt::AutoConnection
)
一个问题:
实际使用时,connect参数2和4传入的却是两个函数指针,char和函数指针并不是同种类型,但是为什么可以赋值,这是因为,上面的声明是以前旧版本的connect函数声明,以前版本中,传参的写法和现在有区别。此时给信号参数传参,要搭配SIGNAL宏,给槽函数传参,要搭配SLOT宏,令传入的函数指针转为char,如下:
SIGNAL(&QPushButton::clicked)
SLOT(&Widget::close)
但是这样写不方便,因此从QT5开始,对上述方法进行简化,不需要写SIGNAL和SLOT宏,给connect提供重载版本,第2和4参数变成泛型参数,允许传入任意类型的函数指针
template <typename Func1, typename Func2>
static inline QMetaObject::Connection connect(const typename QtPrivate::FunctionPointer<Func1>::Object *sender, Func1 signal,
const typename QtPrivate::FunctionPointer<Func2>::Object *receiver, Func2 slot,
Qt::ConnectionType type = Qt::AutoConnection)
4.4.2 信号和槽
Linux 信号 Signal是系统内部的通知机制,是进程间通信的方式。
- 信号源:谁发的信号
- 信号类型:哪种类别的信号
- 信号处理方式:注册信号处理函数,在信号被触发的时候自动调用执行
QT中的信号和Linux中的信号不是一样的概念,但有相似之处。
QT中,信号也是涉及三个要素: - 信号源:由哪个控件发出的信号
- 信号类型:用户进行不同的操作,就可能触发不同的信号;如点击按钮,触发点击信号,在输入框中移动光标,触发移动光标的信号。
- 信号处理方式:槽(slot),本质就是个函数,QT中可以用connect这样的函数,把一个信号和一个槽进行关联,后续只要信号触发,QT会自动执行槽函数,槽函数本质就是个回调函数
需要查看文档确定提供了哪些内置的信号和槽,如果对应类找不到,应该去看其父类:
图中Inherits就是其父类,QPushButton的clicked信号就是在其父类QAbstractButton中定义的
4.4.3 自定义信号和槽
以前版本的Qt中,槽函数必须放到public/private/protected slots中
public slots:
void HandleClick();//槽函数
现在不需要这样,直接用普通成员函数方式写就可以
在QTDesigner中,对控件右键->转到槽可以查看控件提供的所有信号
此时直接点击需要触发的信号->ok,会发现信号对应的槽函数已经被声明、定义好了,直接进行槽函数的具体实现即可,注意,QT中不一定非要通过connect来链接信号和槽,也可以通过函数名的方式进行自动链接,如下:
void MyWidget::on_pushButton_clicked()
{
qDebug()<<"aaa";
}
即on_[控件的ObjectName]_[信号名]来进行链接,注意这种链接方法只对拖拽创建的控件起效,如果是通过代码方式,需要手动connect。
之所以对拖拽起作用,是因为QT自动调用了函数connectSlotsByName(Widget),这个函数在在自动生成的ui_widget.h中调用。
QT中也允许自定义信号,开发中大部分情况都是自定义槽函数,槽函数是用户触发某个操作之后要进行的业务逻辑,开发中自定义信号比较少见,实际开发中很少需要自定义信号,信号对应到用户的某个操作。在GUI中,用户能够进行的操作是可以穷举的,QT内置信号基本上已经覆盖了所有可能的用户操作。
信号是一类特殊函数,只需要写出函数声明,并告诉QT这是一个信号,函数的定义是QT编译过程中自动生成的(自动生成过程,程序员无法干预),信号在QT中是特殊机制,生成的信号函数的实现,要配合QT框架做很多既定操作,信号函数返回值必须是void,有无参数都可以,且可以重载(可以重名)
自定义信号示例如下
signals:
void customSignals();//自定义的信号
signals是QT自己扩展的关键字,qmake时,会调用一些代码的分析/生成工具,扫描到类中包含signals关键字时,会自动将下面的函数声明认为是信号,并给这些信号函数进行自动生成。
发送信号的标准写法如下:
emit customSignals();//发出自定义信号
使用示例:
connect(button, &QPushButton::clicked, this, &MyWidget::HandleClick);//注意,connect要求参数1与参数2必须匹配,即如果button类型是QPushButton,则第二个参数必须是QPushButton的内置信号
connect(this, &MyWidget::customSignals, this, [=](){//使用自定义信号
qDebug("窗口关闭");
});
void MyWidget::HandleClick()
{
this->close();
emit customSignals();//发出自定义信号,这里的emit可写可不写
}
上述代码,进行了两对信号和槽的链接,第一个通过内置信号触发handleClick槽函数,关闭窗口,并发送customSignals信号,第二对的槽函数收到信号,打印窗口关闭
信号和槽其实可以带参数,当信号带有参数,槽的参数必须和信号的参数一致,此时发射信号时就可以给信号函数传递实参,与之对应的这个参数就会被传递到对应槽函数中。
示例如下:
//带参数的信号和槽的定义
signals:
void customSignalsAddParams(const QString &str);//带参数的信号
private slots:
void HandleClickAddParams(const QString &str);//接收带参数的信号
connect(this, &MyWidget::customSignalsAddParams, this, &MyWidget::HandleClickAddParams);//带参数的信号和槽
//自定义槽函数
void MyWidget::HandleClick()
{
this->close();
emit customSignals();//发出自定义信号,emit可写可不写,写上比较标准,可读性更高
emit customSignalsAddParams(QString("发送带参数的信号"));
}
//自定义带参槽函数
void MyWidget::HandleClickAddParams(const QString &str)
{
qDebug()<<str;//打印"发送带参数的信号"
}
上述代码,当点击关闭按钮后,会发送带参信号customSignalsAddParams,其对应的带参槽函数会收到参数,并打印内容"发送带参数的信号"
当信号参数数量超过槽函数数量时,都是可以正常使用的,反之则不行,对上述代码中信号参数数量进行添加
void customSignalsAddParams(const QString &str, QString);//带参数的信号
不对槽进行修改的情况下,程序仍然可以正常允许,但是反之如果让信号参数少于槽函数个数,就无法通过。
这是因为,一个槽函数,有可能绑定多个信号,如果要求参数个数一致,意味着信号绑定到槽的要求就变高了,这样的规则允许信号和槽的绑定更灵活。
个数不一致时,槽函数会按照参数顺序,拿到相应数量的信号参数,要求信号给槽的参数,可以富裕,但不能少(信号参数数量>槽函数数量)。
QT中如果要让某个类可以使用信号和槽,必须在类最开始的地方写下Q_OBJECT宏,这是QT的影星规定,这个宏可以展开很多额外代码。
另外注意,有可能写入Q_OBJECT宏之后直接运行会报错,这时清除、重新构建即可
所谓的信号和槽,终究要解决的问题就是响应用户的操作,这个机制的作用:
- 解耦合,把触发用户操作的控件和处理对应用户的操作逻辑解耦合。写代码应追求“高内聚,低耦合”,低内聚指实现某个功能时,围绕功能的相关代码被放到项目的各个地方;若被放到一起,则为高内聚
- 多对多效果,一个信号,可以connect到多个槽函数上;一个槽函数可以被多个信号connect,其他的GUI框架往往不具备这样的特性,但这种多对多的情况很少用到,绝大部分情况一对一已经够用了。
4.4.4 信号和槽的补充
补充1:
使用disconnect来断开信号和槽的链接,disconnect使用方式与connect类似,disconnect用的比较少,大部分情况下,把信号和槽连接上后,就不用管了,主动断开往往是需要把信号重新绑定到另一个槽函数上
//先将按钮绑定到ChangeWindowTitle
connect(ui->pushButton, &QPushButton::clicked, this, &MyWidget::ChangeWindowTitle);
void MyWidget::ChangeWindowTitle()
{
this->setWindowTitle("窗口标题被修改");
qDebug()<<"修改窗口标题成功";
}
//槽函数,断开修改标题窗口的connect
void MyWidget::on_pushButton_2_clicked()
{
//断开原先的信号槽
bool res=disconnect(ui->pushButton, &QPushButton::clicked, this, &MyWidget::ChangeWindowTitle);
if(res){
qDebug()<<"断开链接成功";
}
else{
qDebug()<<"断开链接失败";
}
//重新绑定信号槽
connect(ui->pushButton, &QPushButton::clicked, this, [=](){
qDebug()<<"已经重新链接";
this->setWindowTitle("修改窗口标题的connect已经断开");
});//重新链接到关闭的槽函数上
}
上述代码,先将按钮绑定,点击后会修改窗口标题,点击另一个按钮,会断开先前按钮的链接,并让他重新链接到另一个槽函数,令其打印“已经重新链接”,并设置新的窗口标题
补充2:
定义槽函数时,也可以使用lambda表达式,其本质是个匿名函数,主要应用在回调函数中
//connect中使用lambda表达式
connect(ui->pushButton, &QPushButton::clicked, this, [](){
qDebug()<<"lambda被执行";
});
上述代码虽然能够运行,但是无法获取到上层作用域中的变量,为了解决这个问题,引入了变量捕获语法,通过变量捕获语法,可以获取上层作用域的变量。方式是将外层作用域变量名写入到"[]"中
int x = 2;
connect(ui->pushButton, &QPushButton::clicked, this, [x](){
qDebug()<<"lambda被执行";
qDebug()<<x;
});
如果lambda中想使用更多的外层变量,可以直接写"[=]",这样可以获取上层作用域的全部变量,如下:
//connect中使用lambda表达式
int x = 2;
connect(ui->pushButton, &QPushButton::clicked, this, [=](){
qDebug()<<"lambda被执行";
qDebug()<<x;
});
写成[&]代表引用的方式来捕获,但很少这么写
注意:lambda语法是c++11中引入的,对于QT5以及更高版本,默认按照C++11编译,如果使用QT4或者更老版本,需要手动在.pro中加上C++11的编译选项
4.4.5 给QPushButton添加快捷键
调用QPushButton::shortCut(QKeySequence)函数
QKeySequence()参数是需要传入字符串,代表按下对应按键会触发快捷键
//实现给按钮添加快捷键上下左右
ui->up->setIcon(QIcon(":/Pic/up.png"));
ui->down->setIcon(QIcon(":/Pic/down.png"));
ui->left->setIcon(QIcon(":/Pic/left.png"));
ui->right->setIcon(QIcon(":/Pic/right.png"));
QPushButton *target = new QPushButton(this);
target->setText("target");
target->setGeometry(50, 50, 100, 50);
connect(ui->up, &QPushButton::clicked, this, [=](){
//获取target的位置
QRect rect = target->geometry();
int x = rect.x();
int y = rect.y();
int width = rect.width();
int height = rect.height();
if(rect.y()-20>=0){
target->setGeometry(QRect(x, y-20, width, height));//向上移动,y减小
}
else{
target->setGeometry(QRect(x, 0, width, height));//移动到边界即可
}
});
connect(ui->down, &QPushButton::clicked, this, [=](){
//获取target的位置
QRect rect = target->geometry();
int x = rect.x();
int y = rect.y();
int width = rect.width();
int height = rect.height();
if(rect.y()+ height +20<this->height()){
target->setGeometry(QRect(x, y+20, width, height));//向上移动,y减小
}
else{
target->setGeometry(QRect(x, this->height()-1-height, width, height));//移动到边界即可
}
});
connect(ui->left, &QPushButton::clicked, this, [=](){
//获取target的位置
QRect rect = target->geometry();
int x = rect.x();
int y = rect.y();
int width = rect.width();
int height = rect.height();
if(rect.x()-20>=0){
target->setGeometry(QRect(x-20, y, width, height));//向上移动,y减小
}
else{
target->setGeometry(QRect(0, y, width, height));//移动到边界即可
}
});
connect(ui->right, &QPushButton::clicked, this, [=](){
//获取target的位置
QRect rect = target->geometry();
int x = rect.x();
int y = rect.y();
int width = rect.width();
int height = rect.height();
if(rect.x()+width+20<this->width()){
target->setGeometry(QRect(x+20, y, width, height));//向上移动,y减小
}
else{
target->setGeometry(QRect(this->width()-1-width, y, width, height));//移动到边界即可
}
});
ui->up->setShortcut(QKeySequence("w"));//添加快捷键
ui->down->setShortcut(QKeySequence("s"));
ui->left->setShortcut(QKeySequence("a"));
ui->right->setShortcut(QKeySequence("d"));
4.5 QWidget
4.5.1 QWidget的enabled属性
描述控件是否处于可用状态,相对概念为禁用,如果一个widget被禁用,则该widget子元素也被禁用。且被禁用的控件不能接收任何用户的输入事件,并且外观为灰色。
API:
- isEnabled():获取到控件的可用状态
- setEnabled():设置控件是否可用,true代表可用,false代表禁用
4.5.2 QWidget的geometry属性
geometry是四个属性的统称:x,y,width,height,其中xy就是左上角位置,width和height是当前控件的尺寸,注意是先写宽度,再写高度
API:
- geometry():获取到控件的位置和尺寸,返回结果是个QRect,包含x、y、width、height,其中x,y是左上角的坐标
- setGeometry(QRect):设置控件的位置和尺寸,可以直接设置一个QRect,也可以分四个属性单独设置
- setGeometry(int x, int y, int width, int height):同上
注意:move函数只能修改位置,setGeometry既可以修改位置,也可以修改控件大小
4.5.3 QWidget的windowFrame
windowFrame指的就是Widget的标题栏,这是操作系统自带的,注意Geometry这个API并不包含这部分,但有的API是包含的。前面所说的Geometry和setGeometry这两个API都不包含这部分。
需要考虑windowFrame的API:
- frameGeometry()
- setFrameGeometry()
//widget的geometry和frameGeometry属性区别
QPushButton *button = new QPushButton(this);
button->setText("windowFrame");
connect(button, &QPushButton::clicked, this, [=](){
QRect rect1 = this->frameGeometry();
QRect rect2 = this->geometry();
qDebug()<<rect1;
qDebug()<<rect2;
});
上述代码查看了widget中两种坐标系的位置和大小,注意必须是点击按钮后才能显示出区别,不能直接在构造函数中获取rect1和rect2,否则会发现二者相同,这是因为构造阶段还没有创建出界面
4.5.4 Widget的windowTitle属性
windowsTitle属性从属于QWidget,windowTitle属性只针对顶层窗口这样的QWidget才有效,对小控件设置windowTitle属性是无效的
API:
- windowTitle():获取窗口的标题
- setWindowTitle(QString):设置窗口的标题
//widget的windowTitle属性
this->setWindowTitle("这是窗口标题");
QPushButton *button = new QPushButton(this);
button->setText("按钮");
button->setWindowTitle("aaa");
运行后会发现窗口标题没有因为给button设置了标题而修改:
4.5.5 Widget的windowIcon属性
windowIcon表示窗口的图标
API:
- windowIcon():获取到控件的窗口图标,返回QIcon对象,QT把各种涉及到的相关概念,封装成类,QIcon表示一个图标
- setWindowIcon(const QIcon &icon):设置控件的窗口图标
注意:这两个API只能针对顶层窗口使用,另外,QIcon是个比较小的对象,创建的目的就是要设置到某个QWidget中,QIcon对象本身释放与否不影响图标最终的显示,QIcon也不支持树对象,因此建议直接在栈上创建
//widget的windowIcon图标
QIcon icon("F:\\Picture\\Pic\\img2.jpg");//注意路径不能为中文
this->setWindowIcon(icon);
效果如下:
设置图标尺寸可以通过setIconSize实现:
//QPushbutton
QPushButton *button = new QPushButton(this);
QPixmap pic(":/Pic/img2.jpg");
QIcon icon(pic);
button->setIcon(icon);//设置图标
button->setIconSize(QSize(pic.width()*0.5, pic.height()*0.5));//设置图标尺寸
bool autoRepeat = button->autoRepeat();
button->setAutoRepeat(true);//设置连续点击
connect(button, &QPushButton::clicked, this, [=](){
qDebug("鼠标被点击");
});
上面设置图标的方式使用了绝对路径,这是不科学的,无法保证开发机和用户电脑上图片路径完全一致,因此需要使用相对路径。这就需要了解qt的qrc机制,该机制本质是想解决两个问题:
- 确保你的图片所在路径在目标用户机器上存在
- 确保你的图片不会被用户删除
给QT项目引入额外的xml文件(后缀名为.qrc),在这个xml中把要使用的图片资源导入进来,并且在xml中进行记录
QT编译项目时,就会根据qrc中描述的图片信息,找到图片内容,并且提取出图片的二进制数据,把这些二进制数据转为c++代码,最终编译到exe中。
qrc缺点是无法导入太大的资源文件,比如不能搞几个GB的视频文件,否则编译时间会非常长
qrc使用方法 - 项目创建qrc文件,文件名不能有中文和特殊符号
- 写入文件名,不要带中文
- 在当前界面中创建一个前缀(prefix),一般就写成"/"即可,就是创建了一个虚拟目录,这个目录没有在电脑上真实存在,是QT自己抽象出来的,qrc本质就是把图片二进制数据转为C++代码,为了方便QT代码访问图片,QT就自己抽象了虚拟目录
- 将要使用的图片导入到刚刚创建的虚拟目录下,点击Add Files,这里必须保证导入的图片必须在resource.qrc文件的同级目录或子目录,如此才能导入成功,如下所示
//widget的windowIcon图标
QIcon icon(":/Pic/img2.jpg");//注意路径不能为中文
this->setWindowIcon(icon);
当导入图片到资源文件后,会发现项目debug文件夹下新生成qrc_resource.cpp文件,命名就是qrc_[资源文件名].cpp,这就是导入图片资源被转为了代码,下图中的字节内容是图片中每个字节的数据,当编译时,cpp文件会被一起编译到exe中,当exe程序运行时,上述图片数据也会加载到内存中。
4.5.6 Widget的windowOpacity
透明效果
API:
- windowOpacity():获取控件的不透明数值,返回值为float,0-1之间,0表示全透明,1表示完全不透明
- setWindowOpacity(float):设置控件的不透明数值
注意这个是针对顶层窗口的,对小控件无效
此外还有2个问题: - 窗口的不透明度变化并非是精确的。
需要了解浮点数在内存中的存储,浮点数的二进制包含三个部分:符号位、有效数字、指数部分,有效数字使用二进制部分,且是指的是小于0的部分,默认整数部分为1,第一个有效数字位表示0.5,第二位表示0.25,因此0.1这样的小数不好表示
4.5.7 Widget的cursor属性
API:
- cursor():获取cursor属性,返回QCursor对象,当悬停在该widget上,就会显示对应形状
- setCursor(const QCursor& cursor):设置widget光标形状,仅在鼠标停留在widget上时生效
- QGuiApplication::setOverrideCursor(const QCursor& cursor):设置全局贯标,对整个程序中所有的widget都会生效,覆盖上面的setCursor设置的内容
//widget的cursor属性
QPushButton *button = new QPushButton(this);
QPixmap pic(":/Pic/img2.jpg");
pic = pic.scaled(20, 20);//对图片进行缩放。这里不是对对象本身进行缩放,所以需要对对象重新赋值
button->setCursor(QCursor(pic, 10, 10));//10,10是热点位置
上述代码可以将目标图片当作鼠标光标进行设置,当然也可以设置成QT内置的光标,可以查看帮助文档查看允许设置的光标类型,在点击时,默认点击的是图片的左上角,传入的10,10是热点位置
4.5.8 Widget的font属性
- font():获取当前widget的字体信息,返回QFont对象
- setFont(const QFont):设置当前widget的字体信息,单位是像素px
QFont的属性: - family:字体家族,比如“楷体”、宋体等
- pointSize:字体大小px
- weight:字体粗细(0,99)
- bold:是否加粗,75/50
- italic:是否倾斜
- underline:是否有下划线
- strikeOut:是否带有删除线
//Widget中的Font
QLabel *label = new QLabel(this);
label->setText("这是一段文字");
QFont font("楷体", 18, 5, true);//楷体,字体大小18, 线粗5,斜体
font.setBold(true);//设置加粗
label->setFont(font);
4.5.9 Widget中的ToolTip
效果:当将鼠标悬停到控件上,能弹出一个提示。
API:
setToolTip():设置提示的内容
setToolTipDuration:设置toolTip提示的时间,单位ms
//widget中的ToolTip
QLabel *label = new QLabel(this);
label->setText("这是一段文字");
label->setToolTip("toolTip提示");//提示内容
label->setToolTipDuration(3000);//提示持续三秒
4.5.10 Widget中的focusPolicy
效果:计算机中的焦点,界面上有个输入框,此时必须选中输入框,接下来键盘按键才会输入到输入框,如果选中别的控件,或者别的窗口,则不会输入。
通常控件获取焦点有两种方式
- 鼠标点击
- 键盘的tab
API: - focusPolicy():获取该widget的focusPolicy,返回QT:FocusPolicy
- setFocusPolicy(QT:FocusPolicy policy):设置widget的focusPolicy
QT:FocusPolicy是个枚举类型: - Qt:NoFocus:控件不会接收键盘焦点
- Qt:TabFocus:通过Tab接收焦点
- Qt:ClickFocus:通过点击接收焦点
- Qt:StrongFocus:可以通过Tab和点击接收焦点
- Qt:WheelFocus:类似于StrongFocus,也通过鼠标滚轮接收焦点
默认是StrongFocus,通常使用默认的已经够用,如需修改可参考下列代码
QLineEdit *edit = new QLineEdit(this);
Qt::FocusPolicy policy = edit->focusPolicy();
qDebug()<<policy;
edit->setFocusPolicy(Qt::NoFocus);//设置得不到焦点
policy = edit->focusPolicy();
qDebug()<<policy;
4.5.11 Widget中的StyleSheet
通过CSS设置widget的样式
CSS是层叠样式表,提供了网页开发时,设置网页样式的方式,QT参考CSS,搞了一套QSS
QSS设置样式也是键值对格式,键值对之间使用";"分割,如下:
如果不知道有哪些属性,可以在帮助文档中查看。在Qt Style Sheet这里
通过代码设置styleSheet可参考下列代码
//widget中的QSS样式styleSheet
QPushButton *button1 = new QPushButton(this);
QPushButton *button2 = new QPushButton(this);
button1->setText("日间模式");
button2->setText("夜间模式");
button1->move(100, 100);
button2->move(200, 100);
connect(button1, &QPushButton::clicked, this, [=](){
this->setStyleSheet("background-color:white;color:black");//背景为白色,文字为黑色
});
connect(button2, &QPushButton::clicked, this, [=](){
this->setStyleSheet("background-color:black;color:white");//背景为黑色,文字为白色
});
最初的:
日间模式:
夜间模式:
QSS中可以直接使用单词来设置颜色:white,black,red,green,blue,yellow等
计算机中,使用RGB方式表示颜色,计算机中,通常使用一个字节来表示R,一个字节表示G,一个字节表示B
观察前面的图片可以看到,QT中Widget的初始背景色并非纯白色,这里可以通过取色器来查看背景色具体是多少数值(使用截图功能时,可以查看光标所在位置的颜色RGB值)
4.6 Radio Button
QRadioButton是单选按钮,可以在多个选项中选择一个。
作为QAbstractionButton和QWidget的子类
属性:
- checkable:是否能够选中
- checked:是否已经被选中,checkable是checked的前提条件
- autoExclusive:是否排他,选中一个按钮后,是否会取消其他按钮的选中,对于QRadioButton来说默认是排他的
使用示例:
//QRadioButton
QRadioButton *radio1 = new QRadioButton(this);
QRadioButton *radio2 = new QRadioButton(this);
QRadioButton *radio3 = new QRadioButton(this);
radio1->setText("a");
radio2->setText("b");
radio3->setText("c");
radio1->setGeometry(100, 50, 20, 20);
radio2->setGeometry(150, 50, 20, 20);
radio3->setGeometry(200, 50, 20, 20);
QLabel *label = new QLabel(this);
label->setText("您选择的按钮是a");
label->move(0, 0);
connect(radio1, &QRadioButton::clicked, this, [=](){
label->setText("您选择的按钮是a");
});
connect(radio2, &QRadioButton::clicked, this, [=](){
label->setText("您选择的按钮是b");
});
connect(radio3, &QRadioButton::clicked, this, [=](){
label->setText("您选择的按钮是c");
});
radio1->setChecked(true);//设置默认选项
上述代码,如果不需要排他,调用setAutoExclusive函数即可
radio的几个信号
- clicked:点击
- pressed:按下
- released:释放
- toggled:状态切换
//QradioButton的clicked、pressed、release、toggled
QRadioButton *radio = new QRadioButton(this);
radio->setText("a");
radio->setGeometry(100, 50, 20, 20);
connect(radio, &QRadioButton::clicked, this, [=](){//点击后触发
qDebug()<<"clicked";
});
connect(radio, &QRadioButton::pressed, this, [=](){//按下就会触发
qDebug()<<"pressed";
});
connect(radio, &QRadioButton::released, this, [=](){//按下之后释放才触发
qDebug()<<"released";
});
connect(radio, &QRadioButton::toggled, this, [=](){//状态切换就会触发
qDebug()<<"toggled";
});
有时可能需要对多个RadioButton进行分租,在组内进行排他,组与组之间互相不影响
//QButtonGroup对单选按钮进行分租
QButtonGroup *group1 = new QButtonGroup(this);//创建三个组
QButtonGroup *group2 = new QButtonGroup(this);
QButtonGroup *group3 = new QButtonGroup(this);
QRadioButton *radio1 = new QRadioButton(this);//创建6个按钮
QRadioButton *radio2 = new QRadioButton(this);
QRadioButton *radio3 = new QRadioButton(this);
QRadioButton *radio4 = new QRadioButton(this);
QRadioButton *radio5 = new QRadioButton(this);
QRadioButton *radio6 = new QRadioButton(this);
radio1->move(10, 10);
radio2->move(50, 10);
radio3->move(90, 10);
radio4->move(140, 10);
radio5->move(190, 10);
radio6->move(240, 10);
radio1->setText("a");
radio2->setText("b");
radio3->setText("c");
radio4->setText("d");
radio5->setText("e");
radio6->setText("f");
group1->addButton(radio1);
group1->addButton(radio2);
group2->addButton(radio3);
group2->addButton(radio4);
group3->addButton(radio5);
group3->addButton(radio6);
上述代码,创建了6个按钮,分别添加到三个分租中,这样就可以实现按钮在三个组内排他,组外不排他
4.7 CheckBox
复选框
//QCheckBox
QCheckBox *box1 = new QCheckBox(this);
QCheckBox *box2 = new QCheckBox(this);
QCheckBox *box3 = new QCheckBox(this);
box1->setText("a");
box2->setText("b");
box3->setText("c");
box1->move(10, 10);
box2->move(50, 10);
box3->move(90, 10);
4.8 LCDNumber
属性:
- intValue:显示的数字值
- value:显示的数字值,和intValue联动,例如value的值1.5,则intValue就是2,需要主义的是设置value和intValue的方法名是display
- digitCount:显示几位数字
- mode:数字显示形式,QLCDNumber::Dec十进制模式,显示常规的十进制数字; QLCDNumber::Hex是十六进制模式,Bin为二进制模式,Oct是八进制模式
- segmentStyle:设置显示风格,QLCDNumber::Flat平面的显示风格,数字呈现在一个平坦表面上;QLCDNumber::Outline轮廓显示风格,数字具有清晰的轮廓和阴影效果;QLCDNumber::Filled填充显示风格,数字被填充颜色并与背景区分开
- smallDecimalPoint:设置比较小的小数点
//QLCDNumber, 每隔1秒减一
QLCDNumber *number = new QLCDNumber(this);
number->move(50, 50);
number->display("12");
QTimer *timer = new QTimer(this);//创建定时器
connect(timer, &QTimer::timeout, this, [=](){
int value = number->intValue();
if(value<=0){
timer->stop();
}
else{
value--;
number->display(value);
}
return;
});
timer->start(1000);//每隔一秒触发一次
通过上述代码,创建一个倒计时,从12到0的计时器。
这里如果想通过创建多线程的方式来修改界面,需要注意:QT中有专门的线程去负责维护更新的(主线程),对于GUI来说,内部包含很多隐藏状态,QT为了保证修改界面的过程中,线程安全是不会受到影响的,QT禁止了其他线程直接修改界面。
对于槽函数,默认情况下,槽函数是主线程调用的,因此在槽函数中改界面是可以的,main中的a.exec就会使主线程进入事件循环,exec会一直循环下去,每执行一次循环,都会有一些固定的事情要做
4.9 QTimer
如果需要每隔1秒执行某个逻辑,可以使用QTimer(定时器),通过这个类创建的对象,会产生timeout这个信号,可以通过start方法开启定时器,并且参数中设置触发timeout信号的周期,结合connect,把这个Timeout信号绑定到需要的槽函数中,就可以执行逻辑,修改LCDNumber的数字了
4.10 QProgressBar
表示一个进度条,属性如下:
minimum:进度条最小值
maximum:进度条最大值
value:进度条当前值
alignment:文本在进度条中的对齐方式
textVisible:进度条数字是否可见
orientation:进度条方向是水平的还是垂直的
invertAppearance:是否朝反方向增长进度条
textDirection:文本的朝向
format:展示的数字格式,%p表示进度的百分比(0-100),%v表示进度的数值(0-100),%m表示剩余事件(单位毫秒),%t表示总时间(单位毫秒)
//QProgressBar, 创建进度条,每隔100ms+1
QProgressBar *bar = new QProgressBar(this);
bar->move(50, 50);
bar->setValue(0);
QTimer *time = new QTimer(this);
connect(time, &QTimer::timeout, this, [=](){
int value = bar->value();
if(value>=100){
time->stop();
return;
}
value++;
bar->setValue(value);
});
time->start(100);
上述代码实现了一个进条,配合QTimer实现,每隔100ms进度条数值+1.
一个问题,为什么头文件不包含QTimer头文件,但是编译不会报错。
QT中有个专门的头文件,这个头文件包含了QT所有类的前置声明,这个头文件不会直接接触到,但是包含其他QT的头文件,会间接包含到这个头文件,后续如果要真正使用QTimer,仍需包含QTimer
主要解决编译速度问题,C++代码编译速度在其他语言横向对比中,是非常慢的,对于一个大规模项目,编译速度可能非常慢。
QT中使用class前置声明的方式,来尽量减少头文件的包含,令每隔头文件包含的其他头文件数量得到一定的降低
4.11 QCalendar Widget
表示一个日历,属性如下:
- selectDate:当前选中的日期
- minimumDate:最小日期
- maximumDate:最大日期
- firstDayOfWeek:每周的第一天是周几
- gridVisible:是否显示表格的边框
- selectionMode:是否允许选择日期
- navigationBarVisible:日历上方标题是否显示
- horizontalHeaderFormat:日历上方标题显示的日期格式
- verticalHeaderFormat:日历第一列显示的内容格式
- dateEditEnabled:是否允许日期被编辑
//QCalendarWidget
QCalendarWidget *calendar = new QCalendarWidget(this);
calendar->move(10, 100);
connect(calendar, &QCalendarWidget::selectionChanged, this, [=](){
QDate date = calendar->selectedDate();//获取选择的日期
qDebug()<<date;
ui->label->setText(date.toString());
});
上述代码可以将选择的日期转为字符串,显示到屏幕
4.12 QCombox
下拉框
属性
- currentText:当前选中的文本
- currentIndex:当前选中的条目下标,从0开始计算,没有选中则为-1
- editable:是否允许修改,设置为true,QComboBox行为非常接近QLineEdit,也可以设置为validator
- iconSize:下拉框图标大小
- maxCount:最多允许有多少个条目
核心方法: - addItem(const QString&):添加一个条目
- currentIndex():获取当前条目的下标,从0开始,没有选中的则返回-1
- currentText():获取当前条目的文本内容
核心信号: - activated(int):用户选择了一个选项时发出,这个时候相当于用户点开下拉框,并且鼠标滑过某个选项,此时还没有确认做出选择
- currentIndexChanged(int):当前选项改变时发出
- editTextChanged(const QString &text):当编辑框中的文本改变时发出(editable为true时有效)
示例:自动加载下拉框选项,注意,需要事先将info.txt添加到资源文件中
//QComboBox从文件中读取信息,添加选项
QComboBox *box = new QComboBox(this);
QFile file(":/info.txt");//读取文件
file.open(QIODevice::ReadOnly);
if(!file.isOpen()){
qDebug()<<"文件打开失败";
}
QString line;
//按行读取文本内容
QTextStream in(&file);
while(!in.atEnd()){
line = file.readLine();
box->addItem(line);//这里要注意传入的参数需要是QString
}
file.close();
4.13 QSpinBox
微调框,有上下两个按钮
关键属性
- value:存储的数值
- singleStep:每次调整的步长,按下一次,数据变化多少
- displayInteger:数字的进制,例如displayInteger设置为10, 则按照10进制表示,设为2则为2进制表示
- minimum:最小值
- maximum:最大值
- suffix:后缀
- prefix:前缀
- wrapping:是否允许换行
- frame:是否带边框
- alignment:文字对齐方式
- readOnly:是否允许修改
- buttonSymbol:按钮上的图标,UpDownArrows上下箭头形式,PlusMinus加减号形式, NoButtons没有按钮
- accelerated:按下按钮是否为快速调整模式
- correctionMode:输入有误时如何调整。QAbsractSpinBox::CorrectToPreviousValue,如果用户输入一个无效值,那么SpinBox会恢复为上一个有效值,例如,若spinBox初始值为1,用户输入-1(无效),则spinBox会恢复为1.QAbstractSpinBox::CorrectToNearestValue若用户输入一个无效值,则会恢复为最接近的有效值
- keyboardTrack:是否开启键盘跟踪,设为true,每次输入都会触发valueChanged()和textChanged()信号
核心信号 - textChanged(QString):文本变化时触发,QString带有前后缀
- valueChanged(int):文本变化时触发,int代表当前数值
示例:
//QSpinBox数量选择
QSpinBox *box = new QSpinBox(this);
box->setMinimum(0);//设置最小、最大值
box->setMaximum(10);
box->setPrefix("要购买");
box->setSuffix("件衣服");
box->setValue(5);
4.14 Date Edit & Time Edit & DateTimeEdit
日期、时间、日期时间编辑
4.14.1 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显示协调世界时,Qt::OffsetFromUTC显示相对于UTC的偏移量(时差)
信号: - dateChanged(QDate):日期改变时触发
- timeChanged(QTime):时间改变时触发
- dateTimeChanged(QDateTime):时间日期任意一个改变时触发
示例,时间计算器,计算两个时间中间的间隔是多少天、多少小时
//QDateTimeEdit,计算两个时间间隔多少天、时、分
QDateTimeEdit *edit1 = new QDateTimeEdit(this);
QDateTimeEdit *edit2 = new QDateTimeEdit(this);
QPushButton *button = new QPushButton(this);
button->move(0, 50);
button->setText("计算间隔");
edit1->move(80, 50);
edit2->move(200, 50);
connect(button, &QPushButton::clicked, this, [=](){
//获取日期
QDateTime oldTime = edit1->dateTime();
QDateTime newTime = edit2->dateTime();
//计算日期的差值
int days = oldTime.daysTo(newTime);//获取天数差值
int secs = oldTime.secsTo(newTime);//获取秒数差值
int hours = secs / 3600 % 24;//获取小时差,然后只需要最后那个余下来的小时数
int minutes = secs % 60;//需要最后那个小时多出来多少分钟
qDebug()<<"相差"<<days<<"天,"<<hours<<"时,"<<minutes<<"分";
});
4.15 Dial
旋钮,仪表盘,可以通过方向键控制移动
核心属性:
- value:持有的数值
- minimum:最小值
- maximum:最大值
- singleStep:按下方向键时改变的步长
- pageStep:按下pageUp/pageDown改变的步长
- sliderPosition:界面上旋钮显示的初始位置
- tracking:外观是否跟随数值变化而变化,一般为true,代表不变化
- wrapping:是否允许循环调整,即如果数值超过最大,是否允许回到最小值
- notchesVisible:是否显示刻度线
- notchTarget:刻度线之间的相对位置,数字越大,刻度线越稀疏
示例:
//QDial旋钮,通过旋钮控制窗口不透明度
QDial *dial = new QDial(this);
dial->setNotchesVisible(true);
connect(dial, &QDial::valueChanged, this, [=](){
float value = dial->value();
value /= 100;
this->setWindowOpacity(value);
});
4.16 Slider
表示滑块
属性:
- minimum:最小
- maximum:最大
- singleStep:按下方向键时改变的步长
- pageStep:按下pageUp/pageDown时改变的步长
- sliderPosition:滑动条显示的初始位置
- tracking:外观是否跟随数值变化
- orientation:滑动条方向是水平还是垂直
- invertedAppearance:是否要翻转滑动条方向
- tickPosition:刻度的位置
- tickInterval:刻度的密集程度
信号:
valueChanged(int):数值改变时触发
rangeChanged(int, int):范围变化时触发
示例,实现两个滑动条,拖动滑块实现对窗口长宽的修改
//QSlider,窗口上放两个滑动条,滑动两个滑动条,可以调整窗口大小
QSlider *slider1 = new QSlider(this);//水平
QSlider *slider2 = new QSlider(this);//垂直
slider1->move(50, 50);
slider2->move(150, 50);
slider1->setOrientation(Qt::Horizontal);//设置为水平
slider2->setOrientation(Qt::Vertical);//设置为垂直
slider1->setMaximum(2000);
slider1->setMinimum(100);
slider1->setValue(800);
slider1->setSingleStep(50);
slider2->setMaximum(1500);
slider2->setMinimum(100);
slider2->setValue(600);
slider2->setSingleStep(50);
connect(slider1, &QSlider::valueChanged, this, [=](int value){
QRect rect = this->geometry();
this->setGeometry(rect.x(), rect.y(), value, rect.height());
});
connect(slider2, &QSlider::valueChanged, this, [=](int value){
QRect rect = this->geometry();
this->setGeometry(rect.x(), rect.y(), rect.width(), value);
});
可以通过QShortCut类实现快捷键的添加
//自定义快捷键,通过快捷键来操作滑动条,滑动条本身可以通过方向键和pageUp、pageDown操作,也可以自定义
QSlider *slider = new QSlider(this);
slider->setMaximum(100);
slider->setMinimum(0);
connect(slider, &QSlider::valueChanged, this, [=](){
qDebug()<<"当前值:"<<slider->value();
});
QShortcut *shortCut1 = new QShortcut(this);//用于指定快捷键
QShortcut *shortCut2 = new QShortcut(this);
shortCut1->setKey(QKeySequence("+"));
shortCut2->setKey(QKeySequence("-"));
connect(shortCut1, &QShortcut::activated, this, [=](){//当快捷键被激活时触发槽函数
int value = slider->value();
slider->setValue(value+1);
});
connect(shortCut2, &QShortcut::activated, this, [=](){//当快捷键被激活时触发槽函数
int value = slider->value();
slider->setValue(value-1);
});
注意定义好shortCut后,用connect来绑定其activated即可,接下来按下快捷键即可触发相应的槽函数
4.17 多元素控件
列表
- QLsitWidget
- QListView
表格 - QTableWidget
- QTableView
树形 - QTreeWidget
- QTreeView
xxWidget与xxView区别:xxView是更底层的实现,xxWidget是基于xxView封装而来。此处xxView是MVC结构的一种经典实现,MVC是软件开发中,非常经典的软件结构的组织形式。M代表数据,V代表视图,C代表控制器,是数据与视图之间的业务流程。
此处xxView只负责实现视图,不负责数据存储表示,更不负责数据和视图之间的交互,因此如果使用xxView,就需要程序员自己实现model和controller的部分,xxWidget基于xxView同时把model和controller实现好了,可以直接使用
二者比较起来,xxWidget使用方便,但功能有限;xxView使用起来更麻烦,但可以根据情况来自由diy,实现更复杂的功能
4.17.1 ListWidget
显示一个纵向显示的列表
核心属性:
- currentRow:当前被选中的是第几行
- count:有多少行
- sortingEnabled:是否允许排序
- isWrapping:是否允许换行
- itemAlignment:元素对齐方式
- selectRectVisible:被选中的元素矩形是否可见
- spacing:元素之间的间隔
核心方法: - addItem(const QString &label)/(QListWidgetItem* item):添加列表元素,列表中每个元素称为一个item
- currentItem():表示当前选中的元素
- setCurrentItem(QListWidgetItem *item):设置选中哪个元素
- setCurrentRow(int row):设置选中第几行元素
- insertItem(const QString&label, int row)/(QListWidgetItem *item, int row):在指定位置插入元素(第row行向后,将新值填入第row行)
- item(int row):返回QListWidgetItem*表示第row行的元素,索引从0开始
- takeItem(int row):删除指定行的元素,返回QListWidgetItem*表示哪个元素被删除
核心信号 - currentItemChanged(QListWidgetItem *new, QListWidgetItem * old):选中不同元素时触发,参数是当前选中和之前选中的元素
- currentRowChanged(int):选中不同元素时触发,参数是当前选中的元素的行数
- itemClicked(QListWidgetItem*item):点击某个元素时触发
- itemDoubleClicked(QListWidgetItem*item):双击某个元素时触发
- itemEntered(QListWidgetItem*item):鼠标进入元素时触发
//QListWidget
QListWidget *list = new QListWidget(this);
list->addItem("C++");
list->addItem("Java");
list->addItem("Python");
list->addItem(new QListWidgetItem("C#"));//两种方式添加元素,在QListWidgetItem中可以设置字体属性,设置图标,设置文字大小,设置是否被选中等
connect(list, &QListWidget::currentRowChanged, this, [=](int row){
qDebug()<<"当前选中的是:"<<row;
});
connect(list, &QListWidget::itemClicked, this, [=](QListWidgetItem *item){
qDebug()<<"当前点击的是:"<<item->text();
});
4.17.2QTableWidget
表示单元格
核心方法
- item(int row, int col):根据行列获取指定的item
- setItem(int row, int col, QTableWidget):根据行列设置表格中的元素
- currentItem():返回被选中的QTableWidgetItem*
- currentRow():返回被选中的是第几行
- currentColumn():返回被选中的是第几列
- row(QTableWidgetItem*):获取指定item是第几行
- column(QTableWidgetItem*):获取指定item是第几列
- rowCount():有多少行
- columnCount():获取列数
- insertRow(int row):在第row行插入新行
- removeRow(int row):删除第row行
- removeColumn(int col):删除第col列
- setHorizontalHeaderItem(int column, QTableWidget*):设置指定列的表头
- setVerticalHeaderItem(int row, QTableWidget*):设置指定行的表头
核心信号 - cellClicked(int row, int column):点击单元格触发
- cellDoubleClicked(int row, int column):双击
- cellEntered():鼠标进入单元格时触发
- currentCellChanged(int row, int column, int previousRow, int previousColumn):选中不同单元格时触发
核心方法 - row():获取当前是第几行
- column():获取当前是第几列
- setText(const QString&):设置文本
- setTextAlignment(int):设置文本对齐
- setIcon(const QIcon&):设置图标
- setSelected(bool):设置被选中
- setSizeHints(const QSize&):设置尺寸
- setFont(const QFont&):设置字体
//QTableWidget
QTableWidget *table = new QTableWidget(this);
table->insertRow(0);//创建行
table->insertRow(1);
table->insertRow(2);
table->insertColumn(0);//创建列
table->insertColumn(1);
table->insertColumn(2);
table->setHorizontalHeaderItem(0, new QTableWidgetItem("a"));//添加表头
table->setHorizontalHeaderItem(1, new QTableWidgetItem("b"));
table->setHorizontalHeaderItem(2, new QTableWidgetItem("c"));
table->setItem(0, 0, new QTableWidgetItem("aa"));//添加数据
table->setItem(0, 1, new QTableWidgetItem("bb"));
table->setItem(0, 2, new QTableWidgetItem("cc"));
qDebug()<<"行数:"<<table->rowCount();
qDebug()<<"列数:"<<table->columnCount();
connect(table, &QTableWidget::cellClicked, this, [=](int row, int col){
QTableWidgetItem* item = table->item(row, col);
qDebug()<<"点击的是"<<row<<"行"<<col<<"列"<<", 内容:"<<item->text();
});
4.17.3 TreeWidget
表示树形控件,里面每个元素为QTreeWidgetItem
核心方法:
- 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():获取元素的父节点
示例:下列代码实现创建一个QTreeWidget,然后用connect实现对选中的元素名称进行显示
//Tree Widget,
QTreeWidget *tree = new QTreeWidget(this);
tree->setHeaderItem(new QTreeWidgetItem(QStringList("动物")));//设置根节点的名称
QTreeWidgetItem *top1 = new QTreeWidgetItem();//创建节点
top1->setText(0, "a");//每个节点可以设置多个列
tree->addTopLevelItem(top1);//将创建的节点添加到顶层节点
QTreeWidgetItem *top2 = new QTreeWidgetItem();//创建节点
top2->setText(0, "b");//每个节点可以设置多个列
tree->addTopLevelItem(top2);//将创建的节点添加到顶层节点
QTreeWidgetItem *top3 = new QTreeWidgetItem();//创建节点
top3->setText(0, "c");//每个节点可以设置多个列
tree->addTopLevelItem(top3);//将创建的节点添加到顶层节点
top1->addChild(new QTreeWidgetItem(QStringList("d")));//给顶层节点添加子节点
top1->addChild(new QTreeWidgetItem(QStringList("e")));
top1->addChild(new QTreeWidgetItem(QStringList("f")));
connect(tree, &QTreeWidget::itemSelectionChanged, this, [=](){//获取选中的元素名称
QTreeWidgetItem *item = tree->currentItem();
int col = tree->currentColumn();
QString name = item->text(col);
qDebug()<<name;
});
4.18 容器控件
多元素控件包含的内容是自定义好的item对象,容器类控件包含的内容是前面学过的各种控件,如QPushButton、QLineEdit、QLabel等
GroupBox:一个带有标题的分组框
QTableWidget:标签页
4.18.1 GroupBox
在GroupBox中可以放入各种控件,此时内部控件的父元素就不是this了,而是GroupBox。
属性
- title:分组框的标题
- alignment:分组框内部内容的对齐方式
- flat:是否为扁平模式
- checkable:是否可以选择,设置为true,会在title前面出现一个可勾选的部分
- checked:描述分组框的选择状态(前提是checkable必须为true)
扁平化效果:会让周围的框消失,只在上面有一条横线
创建QGroupBox的代码如下,实现效果见上图
//QGroupBox
QGroupBox *box = new QGroupBox(this);
box->setGeometry(50, 50, 200, 200);
box->setTitle("汉堡");
QComboBox *comBox = new QComboBox(box);//注意容器类控件内部元素的父元素是容器本身
comBox->addItem("a");
comBox->addItem("b");
comBox->addItem("c");
comBox->setGeometry(0, 20, 100, 20);
box->setFlat(true);
4.18.2 Tab Widget
实现一个带有标签页的控件,每个标签页都可以放一些控件
属性
- tabPosition:描述标签页所在位置,North上方,Sourth下方,West左侧,East右侧
- currentIndex:当前选中了第几个标签页(从0开始)
- currentTabText:当前选中的标签页的文本
- currentTabName:当前选中的标签页的名字
- currentTabIcon:当前选中的标签页的图标
- currentTabToolTip:当前选中的标签页的提示信息
- tabsCloseable:标签页是否可以关闭
- movable:标签页是否可以移动
信号 - currentChanged(int):标签页发生切换时触发,参数为被点击的选项卡编号
- tabBarClicked(int):点击选项卡的标签条的时候触发,参数为被点击的选项卡编号
- tabBarDoubleClicked(int):双击选项卡的标签条时触发,参数为被点击的选项卡编号
- tabCloseRequest(int):标签页关闭时触发,参数为被关闭的选项卡编号
示例:实现一个带有tabWidget,其中有两个按钮,分别为新增和删除标签页按钮,且能够监测到标签页更改信号
//QTableWidget, 创建带有tableWidget,作为标签页,提供两个按钮创建新标签页,分别创建新的标签页和关闭当前标签页,切换标签页时能够感知变化
QTabWidget *table = new QTabWidget(this);
QWidget *widget1 = new QWidget(table);
table->addTab(widget1, "a");
QWidget *widget2 = new QWidget(table);
table->addTab(widget2, "b");
table->setGeometry(40, 40, 200, 200);
QPushButton *b1 = new QPushButton(table);
QPushButton *b2 = new QPushButton(table);//按钮是需要每个标签页中可见的,因此这里他们的父类是Table
b1->move(20, 50);
b2->move(90, 50);
b1->setText("新增标签页");
b2->setText("删除当前标签页");
QLabel *label1 = new QLabel(widget1);//给每个标签页添加一个label
label1->setText("标签页1");
QLabel *label2 = new QLabel(widget2);//给每个标签页添加一个label
label2->setText("标签页2");
connect(b1, &QPushButton::clicked, this, [=](){//添加标签页
int count = table->count();
QString str = "标签页" + QString::number(count+1);
QWidget *widget = new QWidget(table);
table->addTab(widget, str);
QLabel *label = new QLabel(widget);
label->setText(str);
table->setCurrentIndex(count);//跳转到新的标签页
});
connect(b2, &QPushButton::clicked, this, [=](){//删除标签页
int index = table->currentIndex();
table->removeTab(index);//删除当前标签页
});
connect(table, &QTabWidget::currentChanged, this, [=](int index){
qDebug()<<"标签页更改,当前标签页更改后的索引为:"<<index;
});
4.19 布局管理器
之前控件放到界面,都是通过手动方式,而:
- 手动布局方式复杂,不精确
- 无法对窗口大小进行自适应
因此出现了布局管理器,qt中提供了很多种布局管理器 - 垂直布局
- 水平布局
- 网格布局
- 表单布局
4.19.1垂直布局&水平布局QVBoxLayout & QHBoxLayout
核心属性
- layoutLeftMargin:左侧边距
- layoutRightMargin:右侧边距
- layoutTopMargin:上方边距
- layoutBottomMargin:下方边距
- layoutSpacing:相邻元素之间的间距
Layout只用于界面布局,并没有提供信号
注意:每个widget中只能设置一个布局管理器,当代码中创建layout,其实只创建一个layout,如果在qtDesigner中创建layout,则会先创建widget,再在这个新的widget中创建layout
对于窗口中设置了layout的,会在窗口大小调整时对控件的大小、位置进行自适应调整
//QVBoxLayout,创建三个按钮,使用垂直布局管理器管理起来
QPushButton *b1 = new QPushButton(this);
QPushButton *b2 = new QPushButton(this);
QPushButton *b3 = new QPushButton(this);
b1->setText("按钮1");
b2->setText("按钮2");
b3->setText("按钮3");
QVBoxLayout *layout = new QVBoxLayout(this);
layout->addWidget(b1);
layout->addWidget(b2);
layout->addWidget(b3);
布局管理器之间也可以互相嵌套
//layout嵌套
QVBoxLayout *vLayout = new QVBoxLayout(this);
QPushButton *b1 = new QPushButton(this);
QPushButton *b2 = new QPushButton(this);
b1->setText("1");
b2->setText("2");
vLayout->addWidget(b1);
vLayout->addWidget(b2);
QHBoxLayout *hLayout = new QHBoxLayout(this);
QPushButton *b3 = new QPushButton(this);
QPushButton *b4 = new QPushButton(this);
b3->setText("3");
b4->setText("4");
hLayout->addWidget(b3);
hLayout->addWidget(b4);
vLayout->addLayout(hLayout);
4.19.2 网格布局
实现网格布局的效果,达到M*N的效果
属性
- layoutLeftMargin:左侧边距
- layoutRightMargin:右侧边距
- layoutTopMargin:上方边距
- layoutBottomMargin:下方边距
- layoutHorizontalSpacing:相邻元素之间水平方向的间距
- layoutVerticalSpacing:相邻元素之间垂直方向的间距
- layoutRowStretch:行方向的拉伸系数
- layoutColumnStretch:列方向的拉伸系数
//QGridLayout
QPushButton *b1 = new QPushButton(this);
QPushButton *b2 = new QPushButton(this);
QPushButton *b3 = new QPushButton(this);
QPushButton *b4 = new QPushButton(this);
b1->setText("1");
b2->setText("2");
b3->setText("3");
b4->setText("4");
QGridLayout *layout = new QGridLayout(this);
layout->addWidget(b1, 0, 0);//需要指定元素的行列数
layout->addWidget(b2, 0, 1);//需要指定元素的行列数
layout->addWidget(b3, 2, 3);//需要指定元素的行列数
layout->addWidget(b4, 1, 1);//需要指定元素的行列数
效果:
上述创建的布局管理器,其中的控件尺寸均等,当需要创建出尺寸不同的控件时,可通过拉伸系数来设置,拉伸系数相当于设置控件之间尺寸的比例,拉伸系数为0时代表某列/行不参与拉伸
//QGridLayout设置不同尺寸的控件
QPushButton *b1 = new QPushButton(this);
QPushButton *b2 = new QPushButton(this);
QPushButton *b3 = new QPushButton(this);
QPushButton *b4 = new QPushButton(this);
QPushButton *b5 = new QPushButton(this);
QPushButton *b6 = new QPushButton(this);
b1->setText("1");
b2->setText("2");
b3->setText("3");
b4->setText("4");
b5->setText("5");
b6->setText("6");
QGridLayout *layout = new QGridLayout(this);
layout->addWidget(b1, 0, 0);
layout->addWidget(b2, 0, 1);
layout->addWidget(b3, 0, 2);
layout->addWidget(b4, 1, 0);
layout->addWidget(b5, 1, 1);
layout->addWidget(b6, 1, 2);
layout->setColumnStretch(0, 1);//设置拉伸系数,每列之间控件的宽度比为1:1:2,如果设置为0则代表这一列不参与拉伸
layout->setColumnStretch(1, 1);
layout->setColumnStretch(2, 2);
上述代码令每一列控件之间的宽度比为1:1:2
对于行高的拉伸,参考下列代码:
//创建6个按钮,按照3行2列方式排列
QPushButton *b1 = new QPushButton(this);
QPushButton *b2 = new QPushButton(this);
QPushButton *b3 = new QPushButton(this);
QPushButton *b4 = new QPushButton(this);
QPushButton *b5 = new QPushButton(this);
QPushButton *b6 = new QPushButton(this);
b1->setText("1");
b2->setText("2");
b3->setText("3");
b4->setText("4");
b5->setText("5");
b6->setText("6");
this->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);//设置尺寸策略,令控件能够拉伸
QGridLayout *layout = new QGridLayout();
layout->addWidget(b1, 0, 0);
layout->addWidget(b2, 0, 1);
layout->addWidget(b3, 1, 0);
layout->addWidget(b4, 1, 1);
layout->addWidget(b5, 2, 0);
layout->addWidget(b6, 2, 1);
layout->setRowStretch(0, 1);//按照这里的设置,应该为1:1:2,但是实际无效,是因为SizePolicy导致,垂直方向默认无法拉伸
layout->setRowStretch(1, 1);
layout->setRowStretch(2, 2);
this->setLayout(layout);
注意,直接设置拉伸系数会无效,这是由于行之间的拉伸,策略默认是不进行拉伸的,因此要提前设置SizePolicy
4.19.3 表单布局QFormLayout
前端中有form标签,搭配其他input标签,让网页端用户输入数据,提交到服务端
//表单布局, 3行2列
QFormLayout *layout = new QFormLayout(this);
QLabel *label1 = new QLabel("姓名");
QLabel *label2 = new QLabel("年龄");
QLabel *label3 = new QLabel("电话");
QLineEdit *edit1 = new QLineEdit();
QLineEdit *edit2 = new QLineEdit();
QLineEdit *edit3 = new QLineEdit();
layout->addRow(label1, edit1);
layout->addRow(label2, edit2);
layout->addRow(label3, edit3);
QPushButton *b = new QPushButton("提交");
layout->addRow(nullptr, b);
效果:
4.19.4 Spacer
使用布局管理器时,可能需要控件与控件之间添加空白,可以使用QSpacerItem表示
属性
- width:宽度
- height:高度
- hData:水平方向的sizePolicy
- vData:垂直方向的sizePolicy
//QSpacerItem,令两个按钮之间存在空白
QHBoxLayout *layout = new QHBoxLayout();
this->setLayout(layout);
QPushButton *b1 = new QPushButton("按钮1");
QPushButton *b2 = new QPushButton("按钮1");
layout->addWidget(b1);
QSpacerItem *item = new QSpacerItem(100, 20);//空白的宽度100, 高度20
layout->addSpacerItem(item);//因为是要添加到2个按钮中间,因此这句话要放到中间
layout->addWidget(b2);
效果:
5.Qt容器
Qt为了让自己的开发能够顺畅,自行开发了一套轮子,搞了一系列基础类,来支持Qt的开发。
- QString
- QVector
- QList
- QMap
在QT原生api中,涉及到的接口,用的都是QT自己的容器
5.1 QString
内部对字符编码做了处理,而std::string没有
QString中提供了C风格字符串作为参数的构造函数,不显示构造QString,C风格字符串会隐式构造成QString对象
6.QT命名规范
- 起的名称要有描述性,不要用无规律的名字进行描述
- 如果名字比较长,由多个单词构成,就需要使用适当方式区分不同单词(下划线或者驼峰命名法,QT偏好使用大写字母进行单词分割)
7.QT快捷键
- 注释:ctrl+/
- 运行:ctrl+R
- 编译:ctrl+B
- 字体缩放:ctrl+鼠标滚轮
- 查找:ctrl+F
- 整行移动:ctrl+shift+上/下
- 帮助文档:F1
- 自动对其:ctrl+I
- 同名之间的.h和.cpp切换:F4
- 生成函数声明的对应定义:alt+enter
7.1 帮助文档使用
三种使用方法:
- 光标放到要查询的类名/方法名,按F1
- 直接点击左侧边栏中的“帮助”按钮即可
- 直接在开始菜单中点击QT Assistant打开离线版的帮助文档
8. QT坐标系
QT窗口坐标体系就是笛卡尔坐标系(平面直角坐标系):以左上角为原点(0, 0),X向右增加,Y向下增加(左手坐标系)
给某个控件设置位置,需要指定坐标,对于这个控件,坐标系原点就是相对于父窗口/控件的
前文通过代码创建控件时,默认出现位置为(0,0),如下。
修改位置可以通过控件的move方法进行移动
//体会QT坐标系
QPushButton *button = new QPushButton(this);
button->setText("按钮");
button->move(100, 100);//将控件移动到目标位置,单位为像素
9. QT窗口QMainWindow
包含:
- Window Title:窗口标题
- Menu Bar:菜单栏
- Tool Bar Area:工具栏,本质就是把菜单中的常用选项放到工具栏中
- Dock Widget Area:铆接部件,子窗口
- Centeral Widget:中央控件
- status Bar:状态栏
9.1 QMenuBar菜单栏
//QMenuBar
QMenuBar *bar = new QMenuBar();
this->setMenuBar(bar);//设置菜单栏
QMenu *menu = new QMenu(bar);
menu->setTitle("文件");
bar->addMenu(menu);
QAction *act = new QAction();
act->setText("新建");
menu->addAction(act);
connect(menu, &QMenu::triggered, this, [=](QAction *action){
qDebug()<<action->text();
});
上述代码创建菜单栏,并添加文件菜单,包含一个“新建”菜单项
可通过triggered信号绑定菜单项,点击菜单项后触发
9.2 给菜单添加快捷键
一个窗口只有一个菜单栏
一个菜单栏中包含多个菜单QMenu
每个菜单中包含多个菜单项QAction
//菜单添加快捷键
QMenuBar *bar = new QMenuBar();
this->setMenuBar(bar);
QMenu *menu1 = new QMenu("文件(&f)");//创建菜单,添加快捷键
QMenu *menu2 = new QMenu("编辑(&b)");
QMenu *menu3 = new QMenu("视图(&c)");
bar->addMenu(menu1);
bar->addMenu(menu2);
bar->addMenu(menu3);
QAction *a1 = new QAction("菜单项1(&e)");
QAction *a2 = new QAction("菜单项2(&v)");
QAction *a3 = new QAction("菜单项3(&x)");
menu1->addAction(a1);
menu2->addAction(a2);
menu3->addAction(a3);
connect(a1, &QAction::triggered, this, [=](){
qDebug()<<a1->text();
});
connect(a2, &QAction::triggered, this, [=](){
qDebug()<<a2->text();
});
connect(a3, &QAction::triggered, this, [=](){
qDebug()<<a3->text();
});
注:触发菜单项快捷键之前需要先触发菜单快捷键
9.3 添加子菜单
菜单栏->菜单->子菜单->子菜单->菜单项
QMenu也有addMenu,可添加子菜单
//添加子菜单
QMenuBar *bar = new QMenuBar();
this->setMenuBar(bar);
QMenu *menu = new QMenu("父");
QMenu *child = new QMenu("子");
menu->addMenu(child);
bar->addMenu(menu);
child->addAction("子1");
child->addAction("子2");
child->addAction("子3");
9.4 添加分割线&图标
addSpearator
setIcon
//添加分割线&图标
QMenuBar *bar = new QMenuBar();
this->setMenuBar(bar);
QMenu *menu = new QMenu("父");
QMenu *child = new QMenu("子");
menu->addMenu(child);
bar->addMenu(menu);
child->addAction("子1");
child->addSeparator();//添加分割线
child->addAction("子2");
child->addSeparator();
child->addAction("子3");
QIcon icon(":/Pic/left.png");//设置图标
child->setIcon(icon);
QAction *act = child->actions()[0];//获取第0个菜单项
act->setIcon(QIcon(":/Pic/img.png"));
9.5 菜单栏补充
QMenuBar *bar = new QMenuBar();
this->setMenuBar(bar);
若创建的项目勾选自动生成ui文件,上述代码没有问题,否则会存在内存泄漏。
勾选后,QT已生成一个MenuBar,此时使用上述代码会将原有的MenuBar给顶替掉,对象树上不再有原来的MenuBar,无法释放,导致内存泄漏。
//防止创建菜单栏时内存泄漏
QMenuBar *bar = this->menuBar();
if(bar==nullptr){
bar = new QMenuBar();
this->setMenuBar(bar);
}
QMenu *menu = new QMenu("aa");
bar->addMenu(menu);
9.6工具栏
可以有0-多个
创建工具栏:addToolBar
//添加QToolBar
QToolBar *tool = new QToolBar();
this->addToolBar(tool);
QAction *a1 = new QAction("aa");
QAction *a2 = new QAction("bb");
QAction *a3 = new QAction("cc");
tool->addAction(a1);
tool->addAction(a2);
tool->addAction(a3);
a1->setIcon(QIcon(":/Pic/down.png"));
a2->setIcon(QIcon(":/Pic/up.png"));
a3->setIcon(QIcon(":/Pic/left.png"));
connect(a1, &QAction::triggered, this, [=](){
qDebug()<<a1->text();
});
connect(a2, &QAction::triggered, this, [=](){
qDebug()<<a2->text();
});
connect(a3, &QAction::triggered, this, [=](){
qDebug()<<a3->text();
});
当鼠标悬停在图标,应该有提示效果,通过setToolTip方法添加提示
同个QAction,可以同时在工具栏和菜单中出现,此时一个QAction同时是QToolBar的子元素,也是QMenu的子元素,释放时,不会重复delete
可以将工具箱拖动,说明工具栏处于浮动状态。可通过代码来进行设置工具栏出现的初始位置,也可设置允许停放到哪些边缘,可以设置是否允许浮动或移动
//QToolBar的浮动、移动、初始位置、允许停靠位置设置
QToolBar *bar1 = new QToolBar();
QToolBar *bar2 = new QToolBar();
bar1->setAllowedAreas(Qt::ToolBarArea::LeftToolBarArea | Qt::ToolBarArea::RightToolBarArea);//允许停靠在左右
bar2->setAllowedAreas(Qt::ToolBarArea::AllToolBarAreas);
bar1->setFloatable(false);//设置是否可以浮动
bar2->setFloatable(true);
bar1->setMovable(false);//设置是否可以移动(工具栏的三个点会消失)
this->addToolBar(Qt::ToolBarArea::LeftToolBarArea, bar1);//初始时停靠在左侧
this->addToolBar(Qt::ToolBarArea::RightToolBarArea, bar2);//初始停靠在右侧
QAction *act1 = new QAction("a1");
QAction *act2 = new QAction("a2");
QAction *act3 = new QAction("a3");
QAction *act4 = new QAction("a4");
bar1->addAction(act1);
bar1->addAction(act2);
bar2->addAction(act3);
bar2->addAction(act4);
9.7 状态栏
可通过showMessage在状态栏显示文本,其中有个timeout参数,单位ms,代表经过一段时间后消息消失
//QStatusBar
QStatusBar *bar = this->statusBar();
this->setStatusBar(bar);
// bar->showMessage("这是个状态信息", 3000);//3秒后消息消失
QLabel *label1 = new QLabel("这是个标签");
bar->addWidget(label1, 1);//第二个参数是拉伸系数
QLabel *label2 = new QLabel("这是第二个标签");
bar->addWidget(label2, 2);//前后两个标签比例为1:2
QProgressBar *gressBar = new QProgressBar();
gressBar->setRange(0, 100);
gressBar->setValue(50);
bar->addWidget(gressBar);
QPushButton *button = new QPushButton("按钮");
bar->addPermanentWidget(button);//从右边放置控件
9.8 浮动窗口(子窗口)
QDockWidget
给浮动窗口添加控件,不能直接添加,而是要创建单独的QWidget,将控件添加到这个新建的widget中,然后将新建的widget设置到dockWidget中。设置布局时也是需要设置到新建的widget中才可以
//QDockWidget
QDockWidget *widget = new QDockWidget("这是一个浮动窗口");
this->addDockWidget(Qt::DockWidgetArea::RightDockWidgetArea, widget);//设置初始的停靠位置
QWidget *w = new QWidget();//添加控件
widget->setWidget(w);
QVBoxLayout *layout = new QVBoxLayout();//浮动窗口设置布局
w->setLayout(layout);
QLabel *label = new QLabel("这是个label");
QPushButton *button = new QPushButton("按钮");
layout->addWidget(label);
layout->addWidget(button);
widget->setAllowedAreas(Qt::DockWidgetArea::LeftDockWidgetArea | Qt::DockWidgetArea::RightDockWidgetArea);//设置允许停靠位置
9.9 对话框
QDialog
内置对话框
- QFiledialog:文件对话框
- QColorDialog:颜色对话框
- QFontDialog:字体对话框
- QInputDialog:输入对话框
- QMessageBox:消息框
实际开发中,不会在创建项目时继承自QDialog,而是在代码中,创建额外的类,让额外的类继承自QDialog
例子,主窗口中,点击按钮,弹出对话框
注意,QDialog每次按下按钮,都会创建新的QDialog对象,并进行显示,因此程序运行时可以无数次点击按钮,产生无数个这样的对象,从而可能造成内存泄漏。另外,需要明确一下,对话框在关闭按钮被点击时,触发的是隐藏,不会释放他的内存。
//QDialog
QPushButton *button = new QPushButton(this);
button->setText("弹出对话框");
connect(button, &QPushButton::clicked, this, [=](){
QDialog *dialog = new QDialog(this);
dialog->setWindowTitle("这是弹出的对话框");//设置窗口标题
dialog->resize(500, 500);//设置大小
dialog->show();//显示对话框
dialog->setAttribute(Qt::WA_DeleteOnClose);//当关闭窗口时,触发delete操作
});
也可以通过图形化界面的方式来创建自定义对话框,在新建时选择qt->items设计师界面类即可,此时会自动创建.h、.cpp、.ui文件,可通过新的ui文件来更改对话框
对话框分为模态和非模态,model=true(模态)/false(非模态)
模态:弹出对话框时,无法操作父窗口,必须关闭之后才可
非模态:弹出对话框时,用户可以操作父窗口
前面的代码都是非模态对话框,要产生非模态对话框,只需要将show换成exec即可
9.9.1 消息对话框QMessageBox
用于提示重要信息
//QMessageBox
QPushButton *button=new QPushButton(this);
button->setText("弹出消息对话框");
QMessageBox *messageBox = new QMessageBox(this);
messageBox->setWindowTitle("标题");
messageBox->setIcon(QMessageBox::Information);
messageBox->setText("这是文本");
messageBox->setStandardButtons(QMessageBox::Ok | QMessageBox::Close);//设置内置按钮
int value = messageBox->exec();//此时程序会阻塞
messageBox->setAttribute(Qt::WA_DeleteOnClose);
switch(value){
case QMessageBox::Ok:
qDebug()<<"点击OK";
break;
case QMessageBox::Close:
qDebug()<<"点击close";
break;
}
简单方法创建MessageBox
//简单方法创建QMessageBox
QPushButton *button = new QPushButton("弹出对话框", this);
connect(button, &QPushButton::clicked, this, [=](){
int ret = QMessageBox::information(this, "对话框标题", "文本", QMessageBox::Ok | QMessageBox::Cancel);
if(ret == QMessageBox::Ok){
qDebug()<<"ok";
}
else if(ret == QMessageBox::Cancel){
qDebug()<<"cancel";
}
});
9.9.2 颜色对话框QColorDialog
颜色对话框允许用户选择颜色
QColorDialog内置了调色板
//QColorDialog
QPushButton *button = new QPushButton("弹出调色板", this);
connect(button, &QPushButton::clicked, this, [=](){
// QColorDialog *dialog = new QColorDialog(this);
// dialog->exec();
// delete dialog;
QColor color = QColorDialog::getColor(QColor(0, 255, 0), this, "请选择颜色");//这个代码做的事情就是上面两行所做的事情
qDebug()<<color;//打印时发现color通过argb来进行显示,第一个值是不透明度,后面三个值范围[0, 1],对应[0, 255]
// QString style = "background-color: rgb(" + QString::number(color.red()) + ", " + QString::number(color.green()) + ", " + QString::number(color.blue()) + ")";
char style[1024] = {0};
sprintf(style, "background-color: rgb(%d, %d, %d)", color.red(), color.green(), color.blue());
this->setStyleSheet(style);
});
9.9.3 文件对话框QFileDialog
通过QFileDialog可选择一个文件,能够获取到这个文件的路径
- getOpenFileName:打开文件
- getOpenFileNames:打开多个文件
- getSaveFileName:保存文件
//QFileDialog
QPushButton *button1 = new QPushButton("打开一个文件", this);
connect(button1, &QPushButton::clicked, this, [=](){
QString path = QFileDialog::getOpenFileName(this, "选择文件");//注意,保存和打开都是需要额外自行实现的,这里只是获取到路径
qDebug()<<path;
});
QPushButton *button2 = new QPushButton("保存一个文件", this);
button2->move(100, 0);
connect(button2, &QPushButton::clicked, this, [=](){
QString path = QFileDialog::getSaveFileName(this, "保存文件");//注意,保存和打开都是需要额外自行实现的,这里只是获取到路径
qDebug()<<path;
});
9.9.4 字体对话框 QFontDialog
用于选择字体的对话框部件
使用getFont函数来打开对话框,注意第一个参数是个输出参数,代表用户点击的是ok还是cancel
//QFontDialog
QPushButton *button = new QPushButton("弹出字体对话框", this);
connect(button, &QPushButton::clicked, this, [=](){
bool isOk;
QFont font = QFontDialog::getFont(&isOk, this);//第一个参数是个输出参数,看用户点击的是OK还是cancel
qDebug()<<font.family();
qDebug()<<font.pointSize();
qDebug()<<font.bold();//是否加粗
qDebug()<<font.italic();//是否斜体
button->setFont(font);
});
9.9.5 输入对话框 QInputDialog
让用户输入具体数据,可以是整数,可以是浮点数,还可以是字符串
QInputDialog提供了几个静态函数:
- getDouble:让用户输入浮点数
- getInt:输入整数
- getItem:输入字符串
getItem的弹出框有些不同,这是个条目输入框,可以输入下拉列表中的内容,也可以输入自定义内容
//QInputDialog, 条目
QPushButton *button = new QPushButton("弹出输入对话框", this);
connect(button, &QPushButton::clicked, this, [=](){//条目对话框
QStringList items;
items.push_back("1");
items.push_back("2");
items.push_back("3");
items.push_back("4");
QString res = QInputDialog::getItem(this, "条目输入对话框", "输入条目", items);
qDebug()<<res;
});
10. QT系统相关
虽然QT是跨平台的C++开发框架,其很多能力其实是操作系统提供的,只不过QT封装了系统的API
QT系统相关内容包括:
- 事件
- 文件操作
- 多线程编程
- 网咯编程
- 多媒体(音频,视频)
10.1 事件
事件与信号槽类似,用户进行的各种操作,会产生事件,程序员可以给事件关联上处理函数,当事件触发,能够执行对应代码,事件本身是操作系统提供的机制,QT同样把操作系统事件机制进行封装,拿到QT中,QT对于事件机制又进行了进一步封装,就得到了信号槽,信号槽是对事件的进一步封装,事件是信号槽的底层机制。
QT中用QEvent表示事件这个宏观概念,其派生了很多子类,来表示各个具体事件
- QMouseEvent
- QKeyEvent
- QTimerEvent
- QDropEvent
- QInputEvent
- QPaintEvent
10.1.1 事件的处理
让一段代码和某个事件关联起来,当事件触发,就能指定到这段代码
让某个类重写某个事件处理函数,这里用到的是“多态”机制,创建子类,继承自QT已有的类,在子类中重写父类的事件处理函数,后续事件触发过程,就会通过多态机制,执行到自己写的函数中
注意,这里重写事件需要先创建一个继承自父类控件的子类,然后重写相应的事件
void MyLabel::enterEvent(QEvent *event)
{
qDebug()<<"enterEvent";
}
void MyLabel::leaveEvent(QEvent *event)
{
qDebug()<<"leaveEvent";
}
此外,对于在可视化界面中添加的控件,可以通过"提升为",来将他的类型提升成自定义的子类,从而才能够触发重写后的事件函数
还可以通过事件获取鼠标点击的位置
MyLabel::MyLabel(QWidget *parent):QLabel(parent)
{
this->setMouseTracking(true);//开启鼠标追踪
}
void MyLabel::enterEvent(QEvent *event)
{
qDebug()<<"enterEvent";
}
void MyLabel::leaveEvent(QEvent *event)
{
qDebug()<<"leaveEvent";
}
void MyLabel::mousePressEvent(QMouseEvent *ev)
{
qDebug()<<ev->pos();
qDebug()<<ev->globalX()<<" "<<ev->globalY();//以屏幕的左上角为原点
if(ev->button()==Qt::LeftButton){
qDebug()<<"按下左键";
}
else if(ev->button()==Qt::RightButton){
qDebug()<<"按下右键";
}
}
void MyLabel::mouseMoveEvent(QMouseEvent *ev)
{
//若要在不按下鼠标的情况下实时监测移动事件,需要开启鼠标追踪
qDebug()<<ev->pos();
}
void MyLabel::mouseReleaseEvent(QMouseEvent *ev)
{
if(ev->button()==Qt::LeftButton){
qDebug()<<"释放左键";
}
else if(ev->button()==Qt::RightButton){
qDebug()<<"释放右键";
}
}
void MyLabel::mouseDoubleClickEvent(QMouseEvent *ev)
{
if(ev->button()==Qt::LeftButton){
qDebug()<<"双击左键";
}
else if(ev->button()==Qt::RightButton){
qDebug()<<"双击右键";
}
}
需要注意的是鼠标移动事件,默认情况下必须要摁住鼠标后移动才能触发,如果希望不摁住鼠标移动也能触发,需要将鼠标追踪开启。
前面的重写操作,都是在自定义label中完成,此时鼠标只有在label范围内进行动作才能捕获,其实也可以将这些操作放到widget的子类中完成,这样在整个窗口中进行动作时都能捕获
注意,在给QMainWindow设置鼠标移动的时候,必须按下面代码设置鼠标追踪:
this->setMouseTracking(true);
ui->centralwidget->setMouseTracking(true);//在给QMainWindow设置鼠标移动的时候,必须要有这句话
鼠标滚动事件的监听参考下列代码:
void MainWindow::wheelEvent(QWheelEvent *event)
{
qDebug()<<event->delta();//打印鼠标滚轮滚动的距离
}
10.1.2 键盘事件
想要获取用户的键盘按键QShortCut,这是信号槽机制封装过,获取键盘按键的方式
更底层,可以通过事件方式获取用户按键按下的情况
keyPressEvent(QKeyEvent*event)
可以通过event->key()获取按下的字符
对于组合键,QT中使用了modifiers(修饰符)
void MainWindow::keyPressEvent(QKeyEvent *event)
{
if(event->key() == Qt::Key_A){
qDebug()<<"按下了A键";
}
else if(event->key() && event->modifiers()==Qt::ControlModifier){
qDebug()<<"按下了ctrl+A";
}
else if(event->key() && event->modifiers()==Qt::AltModifier){
qDebug()<<"按下了alt+A";
}
}
建议还是使用QShortCut
//使用QShortcut添加组合快捷键
QPushButton *button = new QPushButton("按钮", this);
connect(button, &QPushButton::clicked, this, [=](){
qDebug("这是个按钮");
});
QShortcut *cut = new QShortcut(this);
cut->setKey(QKeySequence("alt+c"));
connect(cut, &QShortcut::activated, this, [=](){
button->click();
});
10.1.3 定时器
QTimer实现了定时器功能,在QTimer背后是QTimerEvent定时器事件进行支撑的
QObject提供了timerEvent这个函数,startTimer启动定时器,killTimer关闭定时器
启动定时器的逻辑
//定时器QTimer
number = new QLCDNumber(this);
number->display("10");
timerId = this->startTimer(1000);//每隔1000毫秒触发一次定时器事件,返回的是一个定时器的身份标识,一个程序可以创建多个定时器
定时器触发后的逻辑
void MainWindow::timerEvent(QTimerEvent *event)
{
//如果一个程序中存在多个定时器,则每个定时器都会触发timeEvent函数,先判断是哪个定时器触发的
if(event->timerId()!=this->timerId){
return;
}
int value = number->intValue();
if(value>=20){
this->killTimer(this->timerId);//停止定时器
return;
}
value++;
number->display(value);
}
10.1.4 窗口移动相关事件
- moveEvent:窗口移动时触发的事件
- resizeEvent:窗口大小改变时触发的事件
事件分发/事件过滤属于QT事件机制背后的一些逻辑,QT也将这部分内容提供了API,有更多操作控件
重写event函数可以获取所有事件,但这种方法若不当使用可能对现有的逻辑造成负面影响,不过有的场景,比如禁用操作,可以考虑使用事件过滤机制
10.2 文件操作
- C中fopen打开文件,fread、fwrite读写文件, fclose关闭文件
- c++中fstream打开文件, << >>读写文件, close关闭文件
- linux中, open打开,read、write读写, close关闭
qt中又封装了一套,用QFile完成文件操作,此外还提供了QSaveFile,若想写一个文件,会自动将内容先写到临时文件,等所有内容都写完,再将旧文件自动删掉,并用新的文件替换旧文件
10.2.1 QFile
- 打开:open
- 读:read/readLine/readAll
- 写:write
- 关闭:close
10.2.2 QFileInfo
QFileInfo可以获取到QT文件的相关属性,类似这样的功能在C/C++标准库中本来都是没有的,所以若想使用类似功能往往要使用系统API,C++17引入模块filesystem
//QFileInfo
QPushButton *button = new QPushButton("弹出对话框", this);
connect(button, &QPushButton::clicked, this, [=](){
QString path = QFileDialog::getOpenFileName(this);
QFileInfo fileInfo(path);
qDebug()<<fileInfo.fileName();//打印文件名
qDebug()<<fileInfo.suffix();//父目录路径
qDebug()<<fileInfo.path();//绝对路径
qDebug()<<fileInfo.size();//文件大小
qDebug()<<fileInfo.isDir();//是否为目录
});
10.3 线程操作
QThread要创建线程,需要创建这个类的实例,需要重点指定线程的入口函数。
创建一个QThread的子类,重写其中的run函数,起到指定入口函数的方式(多态)
常用方法
- run():线程的入口函数
- start():这个操作是真正调用系统API创建线程,操作系统将根据优先级参数调度线程,如果线程已经在允许,这个函数什么也不做
- currentThread():返回一个指向管理当前执行线程的QThread的指针
- isRunning():如果线程正在运行则返回true,否则返回false
- sleep()/msleep()/usleep():使线程休眠,单位为秒/毫秒/微秒
- wait():阻塞线程,知道满足以下条件,与此QThread对象关联的线程已经完成执行(当它从run()返回);如果线程已经完成,这个函数返回true,若线程未启动,也返回true;已经过了几毫秒,如果时间为ULONG_MAX(默认值),那么等待永远不会超时(线程必须从run()返回);若等待超时,该函数返回false
- terminate():终止线程,可立即终止,也可以不立即终止,取决于操作系统的调度策略,在terminate()之后使用QTread::wait()来确保
- finished():当线程结束时会发出该信号,可以通过信号实现线程的清理工作
实例,通过线程发送信号,主线程接收后,令计数增加,进而修改界面
线程函数:
void MyThread::run()
{
//注意在这个run中不能直接修改界面,如果多个线程同时对界面状态进行修改,会导致界面出错
//虽然不可以改界面,但是可以针对时间来进行计时,当每到了一秒,通过信号槽,通知主线程,更新界面
while(true){
sleep(1);
emit notify();//发送信号
}
}
主线程启动子线程:
//QThread
QLCDNumber *lcd = new QLCDNumber(this);
lcd->display("0");
connect(&thread, &MyThread::notify, this, [=](){
int value = lcd->intValue();//获取数值
value++;
lcd->display(value);
});
thread.start();//启动线程
上述代码,子线程每隔1秒发送一次notify信号,主线程每次收到后,将计数增加并将值显示到LCDNumber中
10.3.1 锁
加锁:把多个线程要访问的公共资源,通过锁保护起来,把并发执行变成串行执行
linux中: mutex互斥量
c++11:引入std::mutex
QT同样也提供了对应的锁,针对系统提供的锁进行封装,QMutex
QMutex包含lock(加锁)和unlock(解锁)方法
void MyThread::run()
{
//注意在这个run中不能直接修改界面,如果多个线程同时对界面状态进行修改,会导致界面出错
//虽然不可以改界面,但是可以针对时间来进行计时,当每到了一秒,通过信号槽,通知主线程,更新界面
for(int i=0;i<50000;i++){
mutex.lock();
value++;
mutex.unlock();
// emit notify();//发送信号
}
}
上述代码是加锁的示例,在主线程中会创建两个线程并start,正常情况下,结束后打印value为100000,若不加锁,则不为100000
锁很容易忘记释放,忘记unlock,实际开发中,加锁后,涉及的逻辑可能很复杂,导致下面很容易忘记释放锁
对此,C++11引入std::lock_guard(mutex),相当于std::mutex智能指针
QT中,借助QMutexLocker来自动进行锁的释放
for(int i=0;i<50000;i++){
QMutexLocker locker(&mutex);//一次循环后locker会被释放,其析构函数中会将传入的锁进行解锁操作
value++;
// emit notify();//发送信号
}
10.3.2 条件变量
多个线程之间的调度是无序的,为了能够一定程度的干预线程间的执行顺序,引入条件变量
QWaitCondition
- wait:等待,其内部会自动释放锁 + 等待
- wake:唤醒
- wakeAll:唤醒全部
10.3.3 信号量
QSemaphore semaphore(数量)
- acquire():获取信号量,若满则阻塞
- release():释放信号量
11. QT网络编程
网络编程依靠操作系统提供的一组API(socketAPI)
C++标准库中,并没有提供网络编程的api的封装
进行网络编程时,本质是在编写应用层代码,需要传输层提供支持
传输层的核心协议有UDP和TCP,两个协议差别较大,因此QT提供了两套API完成UDP和TCP开发
**使用QT网络编程的API,需要先在.pro文件中添加network模块,之前学习的QT的各种控件,各种内容,都包含在QtCore模块中,默认就添加了。
**
11.1 UDP
核心API
主要有两个类:QUdpSocket, QNetworkDatagram
对于QUdpSocket,表示一个UDP的socket文件
- bind(const QHostAddress&, quint16):绑定端口
- receiveDatagram():返回QNetworkDatagram,读取一个UDP数据报
- writeDatagram(const QNetworkDatagram&):发送一个UDP数据报
- readyRead:这是个信号,在接收到数据并准备就绪后触发。当收到请求,就会触发,此时可以在槽函数中完成读取请求的操作
对于QNetworkDatagram,表示一个UDP的数据报
- QNetworkDatagram(const QByteArray&, const QHostAddress&, quint16):通过QByteArray,目标IP,目标端口,构造一个UDP数据报,通常用于发送数据时
- data():获取数据报内部持有的数据,返回QByteArray
- senderAddress():获取数据报中包含的对端IP地址
- senderPort():获取数据报中包含的对端端口号
示例:实现UDP回显服务器、客户端
服务器代码:
//创建示例
socket = new QUdpSocket(this);//这里传入this,将他挂到对象树上
//设置标题
this->setWindowTitle("服务器");
//链接信号槽
connect(socket, &QUdpSocket::readyRead, this, [=](){//完成服务器读取请求并解析,根据请求计算响应,把响应返回给客户端
//1.读取请求并解析
QNetworkDatagram requestDatagram = socket->receiveDatagram();//获取数据
QString res = requestDatagram.data();
//2.根据请求计算响应,由于是回显服务器(收到什么,就返回什么),响应不用计算
QString response = process(res);
//3.把响应返回给客户端
QNetworkDatagram responseDatagram(response.toUtf8(), requestDatagram.senderAddress(), requestDatagram.senderPort());//设置返回的内容,要发送给对端的IP、port
socket->writeDatagram(responseDatagram);//写回
//把这次交互的信息写道界面上
QString log = "[" + requestDatagram.senderAddress().toString() + ":" + QString::number(requestDatagram.senderPort()) + "] req: " + res + ", resp:" + response;//构建字符串:[对端的IP, 端口号],请求内容,响应内容
});
//绑定端口号,注意必须先链接信号槽,再绑定,因为一旦绑定,就可以收请求,此时若对端发送,可能少收消息
bool ret = socket->bind(QHostAddress::Any, 9090);//一个端口号只能被一个socket绑定
if(ret<0){
QMessageBox::critical(this, "服务器启动出错", socket->errorString());//显示绑定失败的信息
return;
}
客户端代码:
ui->setupUi(this);
socket = new QUdpSocket(this);
this->setWindowTitle("客户端");
connect(ui->pushButton, &QPushButton::clicked, this, [=](){
QString text = ui->textEdit->toPlainText();
//构造UDP请求数据
QNetworkDatagram requestDatagram(text.toUtf8(), QHostAddress(ip), port);//设置发送的数据、对端的ip、端口
//发送数据
socket->writeDatagram(requestDatagram);
//把发送的消息写入list中
ui->listWidget->addItem("客户端说:" + text);
//将输入框清空
ui->textEdit->setText("");
});
//通过信号槽处理服务器返回的数据
connect(socket, &QUdpSocket::readyRead, this, [=](){
//读取响应数据
QNetworkDatagram data = socket->receiveDatagram();
QString response = data.data();
//显示到界面上
ui->listWidget->addItem("服务端回复: " + response);
});
写的udp服务器大概率无法放到linux上,因为需要确保linux云服务器有图形化界面;但是可以用udp客户端去链接之前写的linux服务器,因为底层的协议相同
11.2 TCP
核心类有两个:QTcpServer,QTcpSocket
QTcpServer用于监听端口,和获取客户端链接
- listen(const QHostAddress &, quint16 port):绑定指定的地址和端口,并开始监听,相当于bind和listen
- nextPendingConnection():从系统中获取一个已经建立好的TCP链接,返回一个QTcpSocket,表示这个客户端的链接,通过这个socket对象完成和客户端的通信,相当于accept
- newConnection:这是个信号,有新的客户端建立链接后触发
QTcpSocket用于客户端与服务端之间的数据交互 - readAll():读取当前接收缓冲区中的所有数据,返回QByteArray对象,相当于read
- write(const QByteArray&): 把数据写入socket中,相当于write
- deleteLater():暂时将socket对象标记为无效,QT会在下次事件循环中析构释放该对象
- readyRead:这是个信号,有数据到达并准备就绪时触发
- disconnected:这是个信号,链接断开时触发
注意,前文所述的“事件循环”,简单理解,可认为是QT程序内部有个生物钟这样的东西,周期性的执行一些逻辑。
实例,实现TCP回显服务端、客户端
服务端代码:
ui->setupUi(this);
//修改窗口标题
this->setWindowTitle("Tcp服务器");
//创建实例
server = new QTcpServer(this);//传入this,让他挂载到对象树
//信号槽,处理链接
connect(server, &QTcpServer::newConnection, this, [=](){
//通过server拿到socket对象,利用该对象进行通信
QTcpSocket *clientSocket = server->nextPendingConnection();//获取对端链接的socket对象
QString log = "[" + clientSocket->peerAddress().toString() + ":" + QString::number(clientSocket->peerPort()) + "] 客户端上线";
ui->listWidget->addItem(log);
//通过信号槽,处理客户端发来请求的情况
connect(clientSocket, &QTcpSocket::readyRead, this, [=](){
QString request = clientSocket->readAll();//获取请求数据
QString response = Process(request);//处理请求数据
clientSocket->write(response.toUtf8());//返回响应数据
QString log = "[" + clientSocket->peerAddress().toString() + ":" + QString::number(clientSocket->peerPort()) + "] req: " + request + ", resp: " + response;
ui->listWidget->addItem(log);//将信息写入日志
//注意,上述接收、发送数据的方式并不严谨,更严谨的方式应该是每次收到数据都放到一个大的字节数组缓冲区,并提前约定好应用层协议的格式(分隔符、长度、其他方法等),再按照协议格式对缓冲区数据进行更细致的解析处理
});
//通过信号槽,处理客户端断开链接的情况
connect(clientSocket, &QTcpSocket::disconnected, this, [=](){//链接断开时触发
//把断开链接的信息通过日志显示出来
QString log = "[" + clientSocket->peerAddress().toString() + ":" + QString::number(clientSocket->peerPort()) + "断开了链接";
ui->listWidget->addItem(log);
//手动释放客户端,否则存在内存泄漏、文件描述符泄漏问题,务必保证delete是这个槽函数的最后异步,也要保证delete肯定能执行到,QT提供了deleteLater
clientSocket->deleteLater();//这个操作不是立即销毁clientSocket,而是告诉QT在下一轮事件循环中,进行上述销毁操作,进入下一轮事件,说明上一轮事件循环肯定结束
});
});
//绑定并监听端口号,注意绑定并监听后,对端若发消息是可以收到的,因此绑定并监听必须是最后一步
bool ret = server->listen(QHostAddress::Any, 9090);
if(!ret){//链接失败
QMessageBox::critical(this, "服务器启动失败", server->errorString());//提示错误信息
return;
}
客户端
#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
//修改标题
this->setWindowTitle("客户端");
//创建socket对象实例
socket = new QTcpSocket(this);
//和服务器建立链接
socket->connectToHost("127.0.0.1", 9090);//注意:该方法并不会阻塞,调用该函数时,系统内核会与对端服务器之间进行三次握手
//需要设置信号槽,从而能够对服务端消息进行响应
connect(socket, &QTcpSocket::readyRead, this, [=](){
QString response = socket->readAll();
ui->listWidget->addItem("服务器消息:" + response);
});
//等待链接建立结果,确认是否链接成功
bool ret = socket->waitForConnected();//该函数会阻塞
if(!ret){
QMessageBox::critical(this, "链接服务器出错", socket->errorString());
exit(1);
}
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::on_pushButton_clicked()
{
//获取输入框的内容
QString text = ui->textEdit->toPlainText();
//将内容发送到对端服务器
socket->write(text.toUtf8());
//消息显示到界面
ui->listWidget->addItem("客户端消息:" + text);
//清空输入框
ui->textEdit->setPlainText("");
}
效果:
11.3 HTTP
HTTP使用比TCP/UDP更多一些,QT也提供了HTTP的客户端,HTTP协议本质就是基于TCP协议实现的,实现一个HTTP客户端/服务器,本质就是基于TCPsocket进行封装
QT只提供了HTTP客户端,没有提供HTTP服务器库
主要用到3个类,QNetWorkAccessManager、QNetworkRequest、QNetworkReply
QNetworkAccessManager提供了HTTP的核心操作
API
- get(const QNetworkRequest&):发起一个HTTP Get请求,返回QNetworkReply对象
- post(const QNetworkRequest&, const QByteArray&):发起一个HTTP POST请求,返回QNetworkReply对象
QNetworkRequest表示一个HTTP请求,不包含body(请求正文)
API:
- QNetworkRequest(QUrl&):通过url构建HTTP请求
- setHeader(QNetworkRequest::KnownHeaders header, const QVariant &value):设置请求头
QNetworkReply表示一个HTTP响应,这个类同时也是QIODevice的子类
API
- error():获取出错状态
- errorString():获取出错原因的文本
- readAll():读取响应body
- header(QNetworkRequest::KnownHeaders header):读取响应指定header的值
此处QNetworkReply还有个重要信号finished会在客户端收到完整响应数据后触发
实例:
#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
//修改标题
this->setWindowTitle("客户端");
//创建实例
manager = new QNetworkAccessManager(this);
}
MainWindow::~MainWindow()
{
delete ui;
}
//发送信息
void MainWindow::on_pushButton_clicked()
{
//获取输入框的url
QUrl url(ui->lineEdit->text());
//构造一个Http请求对象
QNetworkRequest request(url);
//发送请求, 获取响应
QNetworkReply *response = manager->get(request);//发送get请求,注意get不是阻塞函数,只负责发送请求,不负责等待响应回来
connect(response, &QNetworkReply::finished, this, [=](){
if(response->error()==QNetworkReply::NoError){//正确获取响应
QString html = response->readAll();//读取响应
ui->plainTextEdit->setPlainText(html);
}
else{//响应出错
ui->plainTextEdit->setPlainText(response->errorString());
}
//还需要对response进行释放
response->deleteLater();
});
}
11.4 多媒体播放声音
需要先引入multimedia模块,使用QSound类的play()方法播放声音,注意,QSound只能播放.wav格式的音频文件
注意需要引入声音文件到qrc中
#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
sound = new QSound(":/hello.wav", this);
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::on_pushButton_clicked()
{
sound->play();//播放音频
}
12. QSS
可通过QSS设置样式,也可以C++代码设置样式,若冲突,QSS优先级更高
选择器{
属性名:属性值;
}
选择器:先选择某个/某一类控件,接下来进行各种属性进行的设置,都是针对选中的控件生效的
键值对:针对某些样式具体的样式
设置样式时,可以指定某个控件来进行设置,指定后,此时的样式会针对指定控件以及其子控件生效
ui->setupUi(this);
// ui->pushButton->setStyleSheet("QPushButton{color: #ffaa00;}");
this->setStyleSheet("QPushButton{color:red}");
注意,如果第一句解开注释,则只会将第一个按钮颜色改变
12.1 设置全局样式
可将界面上所有的样式集中到一起来组织
全局样式设置是在main.cpp中
#include "mainwindow.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
//全局样式设置
a.setStyleSheet("QPushButton{color:green};");
MainWindow w;
w.show();
return a.exec();
}
若设置了全局样式,然后在某个控件中又设置了其他样式,会怎样?
会同时生效
//设置了全局样式和局部样式
ui->pushButton->setStyleSheet("QPushButton{font-size:50px;}");
此时会在全局样式的基础上进行设置
若设置了全局样式,在某个控件设置的样式和全局样式冲突,会怎样?
会被局部样式给替换,局部样式优先级更高
12.2 界面优化代码分离
之前的代码,样式代码和C++代码是混合在一起的,这样会提高代码的维护成本
可将样式代码放在单独文件中,后续直接让C++代码读取并加载文件内容
步骤
- 创建qrc文件,管理样式文件
- 创建单独的qss文件,添加到qrc中
- 写C++,读取qss文件
资源文件内容:
QPushButton{
color:green;
}
读取并设置样式
#include "mainwindow.h"
#include <QApplication>
#include <QFile>
QString loadQSS()
{
QFile file(":/style.qss");
file.open(QFile::ReadOnly);
QString style = file.readAll();
file.close();
return style;
}
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
//代码分离
a.setStyleSheet(loadQSS());
MainWindow w;
w.show();
return a.exec();
}
更简单的方案,QT designer中集成了QSS,可以直接在ui文件中修改样式
右键控件->改变样式表
排查:
- 全局样式
- 指定控件的样式
- 指定控件的父控件的样式
- qss文件中的样式
- ui文件中的样式
12.3 QSS选择器
- 全局选择器:*,选择所有的widget
- 类型选择器:QPushButton,选择所有的QPushButton和其子类的控件
- 类选择器:.QPushButton,选择所有的QPushButton的控件,不会选择子类
- ID选择器:#pushButton_2,选择objectName为pushButton_2的控件
- 后代选择器:QDialog QPushButton,选择QDialog的所有后代中的QPushButton
- 子选择器:QDialog>QPushButton,选择QDialog的所有子控件中的QPushButton
- 并集选择器:QPushButton, QLineEdit, QComboBox,选择QPushButton, QLineEdit, QComboBox这三种控件,即接下来的样式会针对这三种控件都生效。
若希望同种类型的不同按钮颜色不同,可通过ID选择器实现:
#pushButton{
color:green;
}
#pushButton_2{
color:yellow;
}
并集选择器示例:
QPushButton,QLabel{
color:red;
}
12.4 子控件选择器
如QSpinBox(有两个上调、下调按钮)、QComboBox,这两个按钮就是子控件
12.5 伪类选择器
伪类选择器选择的是控件的状态(符合一定条件的状态)
场景的伪类选择器
- :hover:鼠标放到控件上
- :pressed:鼠标左键按下时
- :focus:获取输入焦点时
- :enabled:元素处于可用状态时
- :checked:被勾选时
- read-only:元素为只读状态时
这些状态可以用!来取反,比如:!hover就是鼠标离开控件时,:!pressed就是鼠标松开时
//伪类选择器
QString style = "QPushButton{color:yellow;}";
style += "QPushButton:hover{color:green;}";//放到控件上时为绿色
style += "QPushButton:pressed{color:blue;}";//按下时为蓝色
a.setStyleSheet(style);
上述代码为设置伪类选择器的示例,效果为鼠标放在按钮上时按钮变为绿色,按下后为蓝色
12.6 样式属性
详见Qt style sheets reference介绍
box model盒子模型
从里向外依次为包含内容、内边距、边框、外边距。
QSS可设置上述边距和边框样式
- margin:设置四个方向的外边距,复合属性(对于Margin可拆成4个属性,即上下左右四个方向),margin:10px代表四个方向的外边距全为10px,若为margin:10px,20px,代表上下为10px,左右为20px
- padding:设置四个方向的内边距
- border-style:边框样式
- border-width:边框粗细
- border-color:边框颜色
- border:复合属性,相当于style+width+color,也分为上下左右四个方向
示例:
//box model样式设置
QString style = "QLabel{border: 5px solid red; padding-left:50px;}";//设置内边距
a.setStyleSheet(style);
style = "QLabel{border:5px solid blue; margin-left:50px;}";
a.setStyleSheet(style);
设置内边距:
设置外边距:
设置样式属性示例
QPushButton{
font-size: 20px;//字体大小
border:2px solid rgb(236, 255, 215);//颜色
background-color:rgb(236, 255, 215);//背景颜色
border-radius:5px;//圆角大小
}
QPushButton:pressed{
background-color:rgb(221, 236, 255);//鼠标按下后的颜色
}
示例,给checkbox设置样式
QCheckBox{
font-size:20px;
}
QCheckBox{
width:20px;
height:20px;
}
QCheckBox::indicator:unchecked {
image: url(:/Pic/nobox.png);
}
QCheckBox::indicator:unchecked:hover {
image: url(:/Pic/nobox.png);
}
QCheckBox::indicator:unchecked:pressed {
image: url(:/Pic/nobox.png);
}
QCheckBox::indicator:checked {
image: url(:/Pic/box.png);
}
QCheckBox::indicator:checked:hover {
image: url(:/Pic/box.png);
}
QCheckBox::indicator:checked:pressed {
image: url(:/Pic/box.png);
}
上述代码给checkbox未被选中和被选中分别设置了两个图片
给顶层控件直接设置背景图会失效,可以添加一个跟顶层控件一样大小的Frame,对其设置背景,背景属性:border-image
示例:实现登录界面
样式QSS代码如下:
QFrame{
border-image:url(:/Pic/background.png)
}
QLineEdit{
background-color:rgb(229, 255, 245);
font-size:20px;
border-radius:5px;
}
QCheckBox{
color:rgb(12, 36, 255);
font-size:20px;
}
QPushButton{
font-size:20px;
color:rgb(255, 147, 205);
background-color:rgb(244, 255, 225);
}
QPushButton:pressed{
color:black;
background-color:rgb(255, 215, 225)
}
效果如下:
13. 绘图API
学习的QT中的各种控件(都是常用的东西,QT已经提前画好了,拿来就能用),本质都是画出来的,实际开发中,可能现有控件无法满足要求,这就需要自己diy一些控件/效果,QT提供的绘图API就是为了解决上述问题的。
绘图API核心类
- QPainter:绘画者,提供一系列的绘图方法,实现会话的动作
- QPaintDevice:要画的内容是画在哪个设备上的,可理解为滑板,QWidget是QPaintDevice的子类
- QPen:画笔,描述画出的线的属性
- QBlush:画刷,描述填充时的属性
画图相关操作,通常不会放在QWidget的构造函数中调用,而是在paintEvent事件处理函数中调用。
paintEvent调用时机: - 控件首次创建时,比如向QWiget上画画,QWidget创建前,画的东西不生效
- 控件被遮挡,再解除遮挡
- 窗口被最小化,再还原
- 控件大小发生改变时
- 主动在代码中调用repaint或update触发事件
13.1绘制各种形状
void MainWindow::paintEvent(QPaintEvent *event)
{
QPainter painter(this);//创建画家,this是指定device,不是指定父类
//绘制线段
painter.drawLine(20, 20, 200, 20);
painter.drawLine(QPoint(200, 100), QPoint(50, 100));
//绘制矩形
painter.drawRect(100, 100, 300, 200);
//绘制圆形
painter.drawEllipse(QPointF(300, 300), 100, 100);
}
效果:
13.2 绘制图片
QT提供了四个类绘制图片:QPixmap、QImage、QBitmap、QPicture。
- QImage:用于IO处理,对IO处理操作优化,且可以用来直接访问和操作像素;
- QPixmap:用于在屏幕上显示图像,对屏幕上显示图像进行优化
- QBitmap:是QPixmap的子类,用于处理颜色深度为1的图像,即只能显示黑白两种颜色
- QPicture:用于记录并重演QPainter命令
图片绘制
QPainter p(this);
QPixmap img(":/Pic/img.png");
// p.drawPixmap(0, 0, img);//从0,0位置开始绘制图片
p.drawPixmap(0,0,this->width(),this->height(), img);//从0,0位置绘制图片,并设置图片大小与窗口大小一致
图片旋转
//图片旋转本质是将QPainter对象进行旋转,绘制的内容也就产生旋转,由于rotate默认绕0,0点旋转,这里需要将原点平移一下
p.rotate(180);//默认是绕0,0点进行旋转
p.translate(-this->width(), -this->height());//坐标系变化,将原点移动到对应位置
p.drawPixmap(0, 0, this->width(), this->height(), img);
}
可以通过QImage的load来进行图片加载,然后用setPixel来对像素点进行修改