重新温习一下Qt的教程,回顾下相关知识
Qt开发环境的搭建和helloworld
- release版本
- 配置icon
Qt多窗口程序
- 在界面上显示的中文仍然使用tr(“中文”),便于使用Qt预言家来翻译整个软件要显示的字符创。
- 通过UI来设置信号和槽
- 在QtDesigner界面可以设置信号和槽
- QMessageBox
- lineEdit设置占位符
- lineEdit的trim和clear
- 类的前置声明
设置信号和槽的方式:
- F4拖动
- 右键转到槽,会要求选择信号
- QtDesigner中选择加号
- 纯代码设置
添加菜单图标
- 菜单如果方无法输入中文,可以复制和粘贴
- 添加资源文件
- 避免外部文件的问题
- 压缩
- 菜单栏、菜单项
布局管理器
- QSplitter
- Vertical Layout
实现Qt文本编辑功能
实现新建、保存和另存为三个功能
实现Qt文本查找功能
- dialog添加layout布局
- refactor功能,在.h中添加声明,然后直接在.cpp中实现定义
%1作为占位符,在显示时会被arg()中的参数替换掉
QMessageBox::warning(this, tr("查找"),
tr("找不到%1").arg(str));文件定位方法,使用定位器。
设置主窗口状态栏
- 添加动作状态提示statusTip
- 显示其他临时信息
Qt键盘、鼠标事件的处理
Qt定时器和随机数
定时器,一种是定时器事件,另一种是使用信号和槽。一般使用了多个定时器时最好使用定时器事件来处理。
2D绘图
渐变填充
绘制文字
绘制路径
绘制图片
QImage、QPixmap、QBitmap和QPicture,它们都是常用的绘图设备。其中QImage主要用来进行I/O处理,它对I/O处理操作进行了优化,而且可以用来直接访问和操作像素;QPixmap主要用来在屏幕上显示图像,它对在屏幕上显示图像进行了优化;QBitmap是QPixmap的子类,用来处理颜色深度为1的图像,即只能显示黑白两种颜色;QPicture用来记录并重演QPainter命令。
QPixmap 缩放、旋转、扭曲等
Qt坐标系统
双缓冲绘图
这一部分并没有完成,源代码也没有正确运行,如果默认都有双缓冲的话,那一开始的代码是怎么回事。
2D绘图部分窗口、视口的研究
图形视图提供大量定制的2D图形项进行管理和相互作用,便于控制多个图形以及之间的相互作用。主要包括Scene、View和Item。
QDrag
QMineData
QCursor
数据库 SQL模块
这一部分目前涉及比较少,暂时不深入研究
该模块可以分为
- 用户接口层:用户接口层的几个类实现了将数据库中的数据链接到窗口部件上,它们是更高层次的抽象,即便不熟悉SQL也可以操作数据库
QSqlQueryModel、QSqlTableModel和QSqlRelationalTableModel - SQL接口层:SQL接口层提供了对数据库的访问
QSqlDatabase、QSqlQuery、A=QSqlError、QSqlField、QSqlIndex和QSqlRecord - 驱动层:驱动层为具体的数据库和SQL接口层之间提供了底层的桥梁
QSqlDriver、QSqlDriverCreator、SQqlDriverCraetorBase、QSqlDriverPlugin和QSqlResult
简介
安装MySQL数据库
利用QSqlQuery类执行SQL语句
有界面和按钮之间的信号和槽控制
- 创建
- 操作结果
- 使用变量
- 批处理
- 事务操作:保证一个复杂的操作的原子性就是对于一个数据库操作序列,这些操作要么全部做完,要么一条也不做,它是一个不可分割的工作单位。
SQL查询模型QSqlQueryModel
简单操作和自定义(读写)
XML
- 使用DOM读取XML文档
- 使用DOM创建和操作XML文档,包含UI操控
- Qt中的SAX,另一种读取XML文档的方法,速度更快
- 使用流读写XML:QXmlStreamReader和QXmlStreamWriter,是SAX地替代品
总的来说,QT提供了3种读取XML文档的方法:
- QXmlStreamReader:一种快速的基于流的方式访问XML文档,特别使用于一次解析
- DOM(Document Object Model):将整个XML文档读入内存,构建一个树结构,允许程序在树上向前向后移动导航,可以允许多次解析。
- SAX(Simple API for XML):提供大量虚函数,以事件的形式处理XML,是为了解决DOM得到内存占用问题提出的。
生成XML文档方法:
- QXmlStreamWriter:最快最方便的XML方法
- DOM,首先在内存中生成DOM树,然后将DOM树写入文件,临时生成树再写入比较麻烦。
- 手动生成XML
Qt元对象和属性系统
Qt 的元对象系统(Meta-Object System)提供了对象之间通信的信号与槽机制、运行时类型信息和动态属性系统。
元对象系统由以下三个基础组成:
- QObject 类是所有使用元对象系统的类的基类。
- 在一个类的 private 部分声明 Q_OBJECT宏,使得类可以使用元对象的特性,如动态属性、信号与槽。
- MOC(元对象编译器)为每个 QObject 的子类提供必要的代码来实现元对象系统的特性。
Qt中的基本数据类型
编译时遇到的一些错误
信号和槽
- qt5中信号和槽的新语法 cnblogs
- New Signal Slot Syntax
- C++_之Qt的信号和槽的详解 cnblogs
- QT实现信号与槽之间传递QVector类型的数据 CSDN 可以不用这么麻烦
- QT子线程与主线程的信号槽通信 传结构体
- Passing QVector
from worker thread to main thread via signal/slot 传QVector - 信号与槽的新语法(Qt5) CSDN
概述
所谓信号槽,实际就是观察者模式。当某个事件发生之后,比如,按钮检测到自己被点击了一下,它就会发出一个信号(signal)。这种发出是没有目的的,类似广播。如果有对象对这个信号感兴趣,它就会使用连接(connect)函数,意思是,将想要处理的信号和自己的一个函数(称为槽(slot))绑定来处理这个信号。也就是说,当信号发出时,被连接的槽函数会自动被回调。这就类似观察者模式:当发生了感兴趣的事件,某一个操作就会被自动触发。
信号和槽是Qt特有的信息传输机制,是Qt设计程序的重要基础,它可以让互不干扰的对象建立一种联系。
槽的本质是类的成员函数,其参数可以是任意类型的。和普通C++成员函数几乎没有区别,它可以是虚函数;也可以被重载;可以是公有的、保护的、私有的、也可以被其他C++成员函数调用。唯一区别的是:槽可以与信号连接在一起,每当和槽连接的信号被发射的时候,就会调用这个槽。Qt5中的新语法可以将信号connect到QObject的任何成员方法,不仅仅是定义的槽。
使用
Qt5中的使用方式:
|
connect()函数最常用的一般形式:
connect(sender, signal, receiver, slot); |
参数:
- sender:发出信号的对象
- signal:发送对象发出的信号
- receiver:接收信号的对象
- slot:接收对象在接收到信号之后所需要调用的函数
信号槽要求信号和槽的参数一致,所谓一致,是参数类型一致。如果不一致,允许的情况是,槽函数的参数可以比信号的少,即便如此,槽函数存在的那些参数的顺序也必须和信号的前面几个一致起来。这是因为,你可以在槽函数中选择忽略信号传来的数据(也就是槽函数的参数比信号的少),但是不能说信号根本没有这个数据,你就要在槽函数中使用(就是槽函数的参数比信号的多,这是不允许的)。
Qt4的使用方式:
int main(int argc, char *argv[]) |
这里使用了SIGNAL和SLOT这两个宏,将两个函数名转换成了字符串。注意到connect()函数的signal和slot都是接受字符串,一旦出现连接不成功的情况,Qt4是没有编译错误的(因为一切都是字符串,编译期是不检查字符串是否匹配),而是在运行时给出错误。这无疑会增加程序的不稳定性。
Qt中使用了新的信号和槽语法,作为旧语法的一种补充。
旧语法
qt5将继续支持旧的语法去连接,在QObject对象上定义信号和槽函数,及任何继承QObjec的对象(包含QWidget)
connect(sender, SIGNAL (valueChanged(QString,QString)),receiver, SLOT (updateValue(QString)) ); |
自定义信号和槽
使用 Qt 的信号槽,实现一个报纸和订阅者的例子:
有一个报纸类Newspaper,有一个订阅者类Subscriber。Subscriber可以订阅Newspaper。这样,当Newspaper有了新的内容的时候,Subscriber可以立即得到通知。
newspaper.h
|
reader.h
|
main.cpp
|
Newspaper类继承了QObject类。只有继承了QObject类的类,才具有信号槽的能力。所以,为了使用信号槽,必须继承QObject。凡是QObject类(不管是直接子类还是间接子类),都应该在第一行代码写上Q_OBJECT。不管是不是使用信号槽,都应该添加这个宏。这个宏的展开将为我们的类提供信号槽机制、国际化机制以及 Qt 提供的不基于 C++ RTTI 的反射能力。
Newspaper类的 public 和 private 代码块都比较简单,只不过它新加了一个 signals。signals块所列出的,就是该类的信号。信号就是一个个的函数名,返回值是void(因为无法获得信号的返回值,所以也就无需返回任何值),参数是该类需要让外界知道的数据。信号作为函数名,不需要在cpp函数中添加任何实现。
Newspaper类的send()函数比较简单,只有一个语句emit newPaper(m_name);。emit是Qt对C++的扩展,是一个关键字(其实也是一个宏)。emit的含义是发出,也就是发出newPaper()信号。感兴趣的接收者会关注这个信号,可能还需要知道是哪份报纸发出的信号?所以,我们将实际的报纸名字m_name当做参数传给这个信号。当接收者连接这个信号时,就可以通过槽函数获得实际值。这样就完成了数据从发出者到接收者的一个转移。
Reader类更简单。因为这个类需要接受信号,所以我们将其继承了QObject,并且添加了Q_OBJECT宏。后面则是默认构造函数和一个普通的成员函数。Qt5中,任何成员函数、static 函数、全局函数和 Lambda 表达式都可以作为槽函数。与信号函数不同,槽函数必须自己完成实现代码。槽函数就是普通的成员函数,因此作为成员函数,也会受到public、private 等访问控制符的影响。(如果信号是private的,这个信号就不能在类的外面连接,也就没有任何意义。)
小结:
- 发送者和接收者都需要是QObject的子类(当然,槽函数是全局函数、Lambda 表达式等无需接收者的时候除外);
- 使用 signals 标记信号函数,信号是一个函数声明,返回 void,不需要实现函数代码;
- 槽函数是普通的成员函数,作为成员函数,会受到 public、private、protected 的影响;
- 使用 emit 在恰当的位置发送信号;
- 使用QObject::connect()函数连接信号和槽。
- 任何成员函数、static 函数、全局函数和 Lambda 表达式都可以作为槽函数
- 槽函数需要和信号一致(参数,返回值),由于信号都是没有返回值,所以槽函数一定没有返回值
信号槽的更多用法
- 一个信号可以和多个槽相连
如果是这种情况,这些槽会一个接一个的被调用,但是它们的调用顺序是不确定的。 多个信号可以连接到一个槽
只要任意一个信号发出,这个槽就会被调用。一个信号可以连接到另外的一个信号
当第一个信号发出时,第二个信号被发出。除此之外,这种信号-信号的形式和信号-槽的形式没有什么区别。- 槽可以被取消链接
这种情况并不经常出现,因为当一个对象delete之后,Qt自动取消所有连接到这个对象上面的槽。 使用Lambda 表达式
在使用 Qt 5 的时候,能够支持 Qt 5 的编译器都是支持 Lambda 表达式的。
我们的代码可以写成下面这样:QObject::connect(&newspaper, static_cast<void (Newspaper:: *)
(const QString &)>(&Newspaper::newPaper),
[=](const QString &name)
{ /* Your code here. */ }
);在连接信号和槽的时候,槽函数可以使用Lambda表达式的方式进行处理。
新旧语法对比
新语法:连接到QObject成员
下面是一种新的方式来连接两个QObjects:
connect(sender, &Sender::valueChanged,receiver, &Receiver::updateValue ); |
它支持:
- 编译期间检查信号和槽是否存在,它们的类型,及Q_OBJECT是否丢失
- 参数能被typedef或不同命名空间指定。
- 如果有隐式转换的参数,会自动转换类型。比如QString到QVariant
- 它可以连接QObject的任何成员方法,不仅仅是定义的槽。
旧版本的不足原因是旧语法connect接收的是两个字符串:
bool QObject::connect ( const QObject * sender, const char * signal, const QObject * receiver, const char * method, Qt::ConnectionType type = Qt::AutoConnection ) [static] |
例如:
connect(slider, SIGNAL(valueChanged(int)), spinbox, SLOT(setValue(int))); |
编译预处理后就是:
connect(slider, "2valueChanged(int)", spinbox, "1setValue(int)"); |
这将导致:
- 即使信号和槽不存在,编译不会出问题。只有运行时会给出警告并返回false,可是大部分用户并不检查返回值。
- 参数必须匹配,比如信号参数是 int,槽参数是 double,语法将会 connect 失败
- 参数类型必须字面上一样,比如说都是int,但是其中一个typedef了一下,或者namespace修饰不同,都会导致连接失败。
而在新语法中避免掉了这些问题。但它不支持:
- 更复杂的语法?你需要指定你的对象类型、
- 非常复杂的语法,比如重载,参见后面。
- 在槽的中默认参数不再被支持。
很典型的例如QSpinBox
重载了信号valueChanged()
,因此不能将
connect(mySpinBox, SIGNAL(valueChanged(int)), mySlider, SLOT(setValue(int)); |
简单转换为
connect( |
需要使用强制类型转换
connect( |
不过这种显示转换可能还是会让某些错误跳过编译器,可以容如下赋值临时变量的方法来保持编译期的检查:
void (QSpinBox::* mySignal)(int) = &QSpinBox::valueChanged; |
当然还有一些其他方法,但最好就是不要重载信号和槽。
传递非基本数据类型
正常情况下信号和槽只能传递通用数据类型,例如QVector
、QString
和结构体等不能传递,编译可能不会出错,但槽函数不会被调用,需要对复杂数据做一些包装处理才行。可以使用
qRegisterMetaType<QVector<float> >("QVector<float>"); |
lambda表达式
C++11中的Lambda表达式用于定义并创建匿名的函数对象,以简化编程工作。首先看一下Lambda表达式的基本构成:
[函数对象参数](操作符重载函数参数)mutable或exception ->返回值{函数体} |
函数对象参数;
[],标识一个Lambda的开始,这部分必须存在,不能省略。函数对象参数是传递给编译器自动生成的函数对象类的构造函数的。函数对象参数只能使用那些到定义Lambda为止时Lambda所在作用范围内可见的局部变量(包括Lambda所在类的this)。函数对象参数有以下形式:- 空。没有使用任何函数对象参数。
- =。函数体内可以使用Lambda所在作用范围内所有可见的局部变量(包括Lambda所在类的this),并且是值传递方式(相当于编译器自动为我们按值传递了所有局部变量)。
- &。函数体内可以使用Lambda所在作用范围内所有可见的局部变量(包括Lambda所在类的this),并且是引用传递方式(相当于编译器自动为我们按引用传递了所有局部变量)。
- this。函数体内可以使用Lambda所在类中的成员变量。
- a。将a按值进行传递。按值进行传递时,函数体内不能修改传递进来的a的拷贝,因为默认情况下函数是const的。要修改传递进来的a的拷贝,可以添加mutable修饰符。
- &a。将a按引用进行传递。
- a, &b。将a按值进行传递,b按引用进行传递。
- =,&a, &b。除a和b按引用进行传递外,其他参数都按值进行传递。
&, a, b。除a和b按值进行传递外,其他参数都按引用进行传递。
int m = 0, n = 0;
[=] (int a) mutable { m = ++n + a; }(4);
[&] (int a) { m = ++n + a; }(4);
[=,&m] (int a) mutable { m = ++n + a; }(4);
[&,m] (int a) mutable { m = ++n + a; }(4);
[m,n] (int a) mutable { m = ++n + a; }(4);
[&m,&n] (int a) { m = ++n + a; }(4);
操作符重载函数参数;
标识重载的()操作符的参数,没有参数时,这部分可以省略。参数可以通过按值(如:(a,b))和按引用(如:(&a,&b))两种方式进行传递。- 可修改标示符;
mutable声明,这部分可以省略。按值传递函数对象参数时,加上mutable修饰符后,可以修改按值传递进来的拷贝(注意是能修改拷贝,而不是值本身)。 - 错误抛出标示符;
exception声明,这部分也可以省略。exception声明用于指定函数抛出的异常,如抛出整数类型的异常,可以使用throw(int) - 函数返回值;
->返回值类型,标识函数返回值的类型,当返回值为void,或者函数体中只有一处return的地方(此时编译器可以自动推断出返回值类型)时,这部分可以省略。 - 是函数体;
{},标识函数的实现,这部分不能省略,但函数体可以为空。
布局是QT界面当中必不可少的一环。我们常常使用QHBoxLayout、QVBoxLayout等进行布局。然而有个问题是,在MainWindow并不能直接进行布局。
-[QT笔记——在QMainWindow内直接添加Layout行不通](https://blog.csdn.net/qq_26399665/article/details/52695042)
发现在QMainWindow中添加的控件不显示,还有QToolBar也可能。
原因是该窗口类中已经有一个Layout对象了,这时候再setLayout是无效的。有三种方法:
1. 调用layout()方法,返回这个窗口内部的layout对象,删除后再setLayout设置自己的Layout对象
2. 直接使用该Layout对象
3. 创建子窗口对象,在这个子窗口对象中使用Layout对象。例如在QMainWindow中,通常是创建一个窗口对象后,调用setCentralWidget来安装该窗口,然后在该窗口中使用自己的Layout。
QWidget *widget = new QWidget();
// method 1
//setCentralWidget(widget);
//centralWidget()->setLayout(mainLayout);
// method 2
widget->setLayout(mainLayout);
setCentralWidget(widget);
## utils
QMessageBox::information(this, tr("Information"), str);