一、元对象系统
1、Qt 的元对象系统提供的功能有:对象间通信的信号和槽机制、运行时类型信息和动态属性系统等。
2、元对象系统是 Qt 对原有的 C++进行的一些扩展,主要是为实现信号和槽机制而引入的, 信号和槽机制是 Qt 的核心特征。
3、要使用元对象系统的功能,需要满足以下三个条件
①、该类必须继承自 QObject 类。
②、必须在类声明的添加 Q_OBJECT 宏,元对象编译器(moc)对象
③、元对象编译器(moc)预处理器,提将qt的程序转换为标准的c++程序,再由标准c++编译器进行编译。
4、元对象系统具体运行原则
①、因为元对象系统是对 C++的扩展,因此使用传统的编译器是不能直接编译启用了元 对象系统的 Qt 程序的,对此在编译 Qt 程序之前,需要把扩展的语法去掉,该功能就 是 moc 要做的事。
然后再由标准 c++编译器进行编译 。
②、moc 全称是 Meta-Object Compiler(元对象编译器),它是一个工具(类似于 qmake), 该工具读取并分析 C++源文件,若发现一个或多个包含了 Q_OBJECT 宏的类的声明, 则会生成另外一个包含了 Q_OBJECT 宏实现代码的 C++源文件(该源文件通常名称 为 moc_*.cpp) ,这个新的源文件要么被#include 包含到类的源文件中,要么被编译键 接到类的实现中(通常是使用的此种方法)。注意:新文件不会“替换”掉旧的文件, 而是与原文件一起编译。
5、其他概念
①、元对象代码:指的是 moc 工具生成的源文件的代码,其中包含有 Q_OBJECT 宏的实 现代码 ②、moc 工具的路径为:F:\app\Qt5.8.0MinGw\5.8\mingw53_32\bin
QObject* btn = new QPushButton;
qDebug()<<btn->metaObject()->className();
QPushButton* pushbtn = qobject_cast<QPushButton*>(btn);
qDebug()<<pushbtn->metaObject()->className();
QTimer* time = new QTimer;
qDebug()<<time->inherits("QTimer");
qDebug()<<time->inherits("QObject");
qDebug()<<time->inherits("QLineText");
qDebug()<<time->inherits("QLabel");
元对象系统与反射机制
reflection 模式(反射模式或反射机制):是指在运行时,能获取任意一个类对象的所有类型信息、属性、成员函数等信息的一种机制。
①、元对象系统提供的功能之一是为 QObject 派生类对象提供运行时的类型信息及数据成 员的当前值等信息,也就是说,在运行阶段,程序可以获取 QObject 派生类对象所属 类的名称、父类名称、该对象的成员函数、枚举类型、数据成员等信息,其实这就是 反射机制。
②、因为 Qt 的元对象系统必须从 QObject 继承,又从反射机制的主要作用可看到,Qt 的 元对象系统主要是为程序提供了 QObject 类对象及其派生类对象的信息,也就是说不 是从 QObject 派生的类对象,则无法使用 Qt 的元对象系统来获取这些信息。
二、属性系统
1、属性与数据成员相似,但是属性可使用 Qt 元对象系统的功能。他们的主要差别在于存取方式不同,比如属性值通常使用读取函数(即函数名通常以 get 开始的函数)和设置函数 (即函数名通常以 set 开始的函数)来存取其值,除此种方法外,Qt 还有其他方式存取属性 值。
2、在 Qt 中属性和数据成员是两个不同的概念,他们可以相关联也可以没有联系,比如名为 a 的属性,与数据成员 a,虽然他们名称相同,若他们之间没有产生关联,则数据成员 a 与属性 a 是完全不相关的,通常,一个属性都有与之相关联的数据成员,而采用的命名规 则通常是加上 m_前缀,比如属性名为 a,则与之相关联的数据成员名称通常为 m_a。
3、属性值可使用以下方式进行存取
可使用 QObject::property 和 QObject::setProperty 函数进行存取
若属性有相关联的存取函数,则可使用存取函数进行存取
属性还可通过元对象系统的 QMetaObject 类进行存取。
若属性与某个数据成员相关联,则可通过存取普通数据成员的值来间接存取属性的 值。
注意:Qt 中的类,只有属性没有数据成员,因此只能通过前面三种方式对属性值进 行修改。
三、信号与槽
元对象系统支持的,对象间通信的机制
为此 Qt 引入了一些关键字,他们是 slots、signals、emit,这些都不是 C++关键字,是 Qt 特有的,这些关键字会被 Qt 的 moc 转换为标准的 C++语句。
信号和槽其实是观察者模式的一种实现,最大限度的弱化了类之间的耦合关系
类中做槽函数的成员函数一般写在public slots下,Qt5以及以上版本可以不写public slots。
1. 相互关系
信号是被观察者 槽是观察者
用一个信号可以连接多个槽(一对多)
多个信号可以连接一个槽(多对一)
信号可以和信号连接(转嫁)
连接可以被disconnect函数删除
注:ui连接方式和connect连接会同时生效
2.disconnect函数
static bool disconnect(const QObject *sender, const char *signal,
const QObject *receiver, const char *member);
0 可以用作通配符,分别表示“任意信号”、“任何接收对象”或“接收对象中的任何插 槽”。
sender 永远不会是 0
disconnect(&ma, 0, 0, 0); 断开与对象 ma 中的所有信号相关联的所有槽。 disconnect(&ma , SIGNAL(s()), 0, 0);断开与对象 ma 中的信号 s 相关联的所有槽。 disconnect(&ma, 0, &mb, 0); 断开 ma 中的所有信号与 mb 中的所有槽的关联
3.Qt::ConnectionType
type:用于指明信号和槽的关联方式,它决定了信号是立即传送到一个槽还是在稍后 时间排队等待传送。关联方式使用枚举 Qt::ConnectionType 进行描述,下表为其取值 及意义
每个线程都有自己的事件队列,线程在事件队列中接受信号,
线程的事件循环:
每一个线程都可以有它自己的事件循环。初始化线程(GUI线程)使用QCoreApplication::exec()来开启它的事件循环(对于独立的对话框界面程序,也可以使用QDialog::exec()).其他的线程可以使用QThread::exec()来开启一个事件循环。与QCoreApplication相似,QThread提供了一个exit()函数和一个quit()槽。在一个线程中使用事件循环,使得该线程可以使用那些需要事件循环的非GUI类(例如,QTimer、QTcpSocket、QProcess等等)。也使得该线程可以关联任意一个线程的信号到一个指定线程的槽
每一个线程都有自己的事件队列 ,线程通过事件队列接收信号 ,信号在事件循环中被处理
每一个线程都有自己的事件队列 ,线程通过事件队列接收信号 ,信号在事件循环中被处理
Qt::AutoConnection: 默认值,
使用这个值则连接类型会在信号发送时决定。如果接收者和发送者在同一个线程,则自动使用Qt::DirectConnection类型。如果接收者和发送者不在一个线程,则自动使用Qt::QueuedConnection类型。
Qt:: DirectConnection (立即调用)
-直接在发送信号的线程中调用槽函数,等价于槽函数的实时调用!
Qt:: QueuedConnection (异步调用)
-信号发送至目标线程的事件队列,等待事件循环处理;当前线程继续向下执行!
Qt:: BlockingQueuedConnection (同步调用)
-信号发送至目标线程的事件队列,由目标线程处理; 当前线程等待槽函数返回,之后继续向下执行!
Qt::UniqueConnection (单一连接)
-描述
• 功能与AutoConnection相同,自动确定连接类型
• 同一个信号与同一个槽函数之间只有一个连接 (默认情况下,同一个信号可以多次连接到同一个槽函数 ,多次连接意味着同一个槽函数的多次调用 ),而非一次连接
connect(ui->startBtn,&QPushButton::clicked,this,&MainWindow::on_startBtn_clicked,Qt::UniqueConnection);
connect(ui->pushButton_3,&QPushButton::clicked,this,&MainWindow::on_startBtn_clicked,Qt::ConnectionType(Qt::UniqueConnection|Qt::AutoConnection));
在槽函数中,使用QObject::sender()可以获取信号发送者的指针
QSpinbox* spinbox = qobject<QSpinBox*>(sender());
线程安全:多个线程访问共享资源时,程序仍然能够正常工作不会出错。
qt信号与槽本身线程安全,如果接收者发送者不在同一线程使用Qt::DirectConnection,会造成线程安全问题,
四、对象树与生命期
1、为什么要使用对象树:GUI 程序通常是存在父子关系的,比如一个对话框之中含有按钮、 列表等部件,按钮、列表、对话框等部件其实就是一个类的对象(注意是类的对象,而非 类),很明显这些对象之间是存在父子关系的,因此一个 GUI 程序通常会由一个父对象维护着一系列的子对象列表,这样更方便对部件的管理,比如当按下 tab 键时,父对象会依据子对象列表令各子对象依次获得焦点。当关闭对话框时,父对象依据子对象列表,找到 每个子对象,然后删除它们。在 Qt 中,对对象的管理,使用的是树形结构,也就是对象树。
2、子对象和父对象:本小节的父/子对象是相对于由对象组成的树形结构而言了,父节点对象被称为父对象,子节点对象被称为子对象。注意:子对象并不是指类中的对象成员。
QObject提供了parent()、children()、findChildren()等函数。
一、组合模式与对象树
1、组合模式指的是把类的对象组织成树形结构,这种树形结构也称为对象树,Qt 使用对象 树来管理 QObject 及其子类的对象。注意:这里是指的类的对象而不是类。把类组织成树 形结构只需使用简单的继承机制便可实现。
2、使用组合模式的主要作用是可以通过根节点对象间接调用子节点中的虚函数,从而可以间 接的对子节点对象进行操作。
3、组合模式的基本思想是使用父类类型的指针指向子类对象,并把这个指针存储在一个数组 中(使用容器更方便),然后每创建一个类对象就向这个容器中添加一个指向该对象的指针。
组合模式就运用了树形结构,该模式的核心思想是:将多个对象组合成树形结构,以此结构来表示“整体-部分”之间的层次关系。
其实根节点和树枝节点本质上属于同一种数据类型,可以作为容器使用;而叶子节点与树枝节点在语义上不属于用一种类型。但是在组合模式中,会把树枝节点和叶子节点看作属于同一种数据类型(用统一接口定义),让它们具备一致行为。
1、使用组合模式可以让用户可以使用统一的方式处理整个树形结构的个别对象和组合对象,从而简化客户端的操作。
2、组合模式具有较强的扩展性,当我们想要更改组合对象时,只需要调整内部的层次关系即可,客户端不需要作出任何改动。
3、客户端不用考虑组合中的细节,通过添加节点和叶子就可以创建出复杂的树形结构。
4、当需要处理的对象是树形结构时可以考虑使用组合模式。
5、节点和叶子节点存在很大差异的情况下不建议使用组合模式。
A ma1; B mb1;
mb1.setParent(&ma1); //在栈上把 ma1 指定为 mb1 的父对象,此处父对象创建于子对象之前。
对象树的组织规则:
①、每一个 QObject 对象只能有一个父 QObject 对象,但可以有任意数量的子 QObject 对 象。比如 A ma; B mb; C mc; ma.setParent(&mb); //将对象 ma 添加到 mb 的子对象列表中, ma.setParent(&mc); //该语句会把 ma 从 mb 的子对象列表中移出,并将其添加到 mc 的子对象列表中。
②、QObject 对象会把指向各个子对象地址的指针放在 QObjectList 之中。QObjectList 是 QList的别名,QList 是 Qt 的一个列表容器。
在 Qt 中,当一个父对象被删除时,它会自动删除它所有的子对象,包括 QWidget 对象和其他对象。在删除父对象时,Qt 会先遍历它的子对象列表,递归删除每个子对象,然后再删除父对象本身。因此,先删除子对象,再删除父对象。
tips:
回调函数:使用一个指向函数的指针去调用需要的函数, 这样就可以调用任意名称的函数(只要函数类型与指针相同即可),
进程有独立的地址空间,线程共享一个地址空间
多个线程独享栈区,共享堆区、代码区、全局区
1.如何理解qt事件
gui程序是由事件驱动的
事件:qt是基于c++跨平台界面应用程序框架,主要用于开发带窗口的应用程序,我们使用的基于窗口的应用程序,比如窗口大小变化或者点击鼠标按下键盘,都会触发对应的事件。qt的窗口可以是嵌套的,会有若干个子窗口,事件会被发送到对用的窗口中去。
事件派发(qt的应用程序对象把事件发送到对用的窗口)
每个qt程序对应的唯一个QApplication对象,对象被创建后会调用exec函数,启动主事件循环来检测应用程序的事件。
事件发生后,应用程序对象调用notify()函数将事件发送到指定窗口。
->事件过滤(可以选择对事件进行过滤)
事件派发过程中窗口可以用evenFilter()函数对事件进行过滤,默认不对任何事件进行过滤。
->事件分发
事件发送到指定窗口后,事件event()函数在对事件进行细分,事件分发器
->事件处理
窗口拿到细分的事件后执行对应的事件处理函数。
2.qt信号与槽底层机制
define signal = public
define slot = ""
①、该类必须继承自 QObject 类。
②、必须在类声明的添加 Q_OBJECT 宏,元对象编译器(moc)对象
类对象经过moc编译器生成了对应的moc文件 ,object宏提供了静态元对象 staticMetaObject和获取方式,为信号和槽补全了部分逻辑,组成了完整的类对象,包含了当前类信息和信息索引表、回调函数
步骤:计算信号和槽的绝对索引,将信号与槽组成的连接放在信号的私有元对象的信号槽存放容器QObjectConnectListVector的指定位置
这是一个Vector容器,它里面的基本单元都是ConnectionList类型的数据,ConnectionList的个数与该QObject对象的signal个数相同。每个ConnectionList对应一个信号,它记录了连接到这个信号上的所有连接。前面已经看到ConnectionList的定义中有两个重要成员:first和last,他们都是Connection 类型的指针,分别指向连接到这个信号上的第一个和最后一个连接。所有连接到这个信号上的连接以单向链表的方式组织了起来,Connection结构体中的nextConnectionList成员就是用来指向这个链表中的下一个连接的。
信号与槽是松耦合,信号发送者不需要去知道接收者信息,回调函数是紧耦合,直接调用目标对象的特定函数
信号和槽机制运行速度比回调函数慢
回调函数都是同步执行的,槽函数可能是异步执行的,直接连接是同步执行
当一个对象发射一个信号时,元对象系统会根据信号的名称找到该信号对应的索引,并根据索引找到连接到该信号的槽函数
观察者模式,激发信号的Qt对象无须知道是哪个对象的哪个槽函数需要接收它发出的信号,它只需要做的是在适当的时间发送适当的信号就可以了,而不需要知道也不关心它的信号有没有被接收到,更不需要知道哪个对象的哪个槽接收了信号。
信号槽机制,同回调函数相比,信号和槽机制运行速度有些慢。遍历,通过传递一个信号来调用槽函数将会比直接调用非虚函数运行速度慢10倍。原因如下:- 需要定位接收信号的对象;- 安全地遍历所有的关联(一个信号关联多个槽的情况);- 编组/解组传递的参数;- 多线程的时候,信号可能需要排队等待。
在没有信号槽机制的时代,C++对象间的交互一般使用回调函数来实现。使用某对象时,用指针指向另一个对象的函数,这个函数就称为回调函数。使用回调函数有个弊端,当某个对象被多个对象通信时,需要一个容器来存放多个对象的回调函数???。维护这个容器使得代码编写效率低、扩展性弱。
qt信号槽的本质就是回调函数。
3.deletelater和delate区别
delete是c++和qt公有的一个关键字标识符,即实时析构,delete会马上销毁目标对象。
而QT里的deletelater的原理是:QObject::deleteLater()并没有将对象立即销毁,而是向主消息循环发送了一个event,下一次主消息循环收到这个event之后才会销毁对象。 这样做的好处是可以在这些延迟删除的时间内完成一些操作,坏处就是内存释放会不及时。
deleteLater
中把this
作为QDeferredDeleteEvent
消息的reciever,那当下一轮事件循环派发消息,reciever
的事件处理函数event
就会被调用来处理DeferredDelete
类型的消息,处理动作就是delete this
。
在删除下操作完成后,qt会自动将指针设置为nullptr,以确保不会访问已删除的对象。杜绝出现野指针。
3.对象树结构
当创建一个QObject对象时,可以提供一个父对象,我们创建的这个QObject对象会自动添加到其父对象的Children()列表中,当父对象析构时,这个列表的所有对象都会被析构。
当父对象析构的时候,这个子对象列表中的所有对象都会被析构,当析构子对象的时候,会自动从父对象的子对象列表中删除。
当QT对象被销毁时,将自己从父对象的子对象链表中删除,将自己的子对象链表中的所有对象销毁。QT对象销毁时解除和父对象之间的父子关系,并销毁所有的子对象。
C++中规定了析构顺序应该按照其创建顺序的相反过程
在C++中,不存在继承关系时,按照创建顺序,先创建的先构造,先构造的后析构,
- 先创建父对象再创建子类对象,并且在创建子对象时就指定父对象;
- 尽量在堆上创建子对象;
5.我们只声明了信号,而没有定义,为什么还能用?
Qt使用元对象系统来实现信号与槽的连接,它通过在编译时生成额外的代码来支持信号与槽的连接。
6.事件循环
qt程序都会有一个QApplication对象,
事件循环一般从exec函数开启,程序在exec中无限循环,事件循环能够接收事件并处理。当事件太多时会被放到一个队列中,被称为事件循环队列,
事件循环一般用exec()函数开启。QApplicaion::exec()、QMessageBox::exec()都是事件循环。其中前者又被称为主事件循环。事件循环首先是一个无限“循环”,程序在exec()里面无限循环,能让跟在exec()后面的代码得不到运行机会,直至程序从exec()跳出。从exec()跳出时,事件循环即被终止。QEventLoop::quit()能够终止事件循环。其次,之所以被称为“事件”循环,是因为它能接收事件,并处理之。当事件太多而不能马上处理完的时候,待处理事件被放在一个“队列”里,称为“事件循环队列”。当事件循环处理完一个事件后,就从“事件循环队列”中取出下一个事件处理之。当事件循环队列为空的时候,它和一个啥事也不做的永真循环有点类似,但是和永真循环不同的是,事件循环不会大量占用CPU资源。事件循环的本质就是以队列的方式再次分配线程时间片。
事件队列是用于存储待处理事件的数据结构,通常是一个队列。在 Qt 中,事件队列用于存储各种类型的事件,包括用户输入事件(如鼠标点击、键盘输入)、定时器事件、系统事件等。
事件队列的主要作用是将事件从产生事件的源头(例如用户输入设备、定时器、系统信号等)传递到事件接收者(例如窗口部件、自定义对象、线程等),并在适当的时候将事件发送给事件接收者进行处理。当一个事件发生时,Qt 会将其封装成一个事件对象,并将其添加到事件队列中。
在 Qt 中,子层事件循环通常在主线程以外的线程中创建和使用,以确保主线程的事件循环不会被阻塞。通过使用子层事件循环,可以实现多线程编程中的异步事件处理和事件等待,保持界面的响应性,并且能够在需要时手动控制事件循环的执行。
7.QT多线程有哪些实现方法?
1.创建线程子类,继承QThread,重写run函数,在主线程中创建子线程对象,启动子线程,调用start方法
2.创建工作类从object类派生,在主线程中创建子线程,将工作类对象移动至主线程movetothread,调用子线程start方法,在调用工作类函数,
8.QT插件原理
插件作用:扩展主程序功能,动态加载,面向接口编程而不是面向实现编程,可以理解为模块
分为静态库插件和动态库插件,在编译时加载静态库插件,运行时加载动态库插件
插件管理器需要自行实现
Q_INTERFACES对接口ID进行判断,进行上行转换,返回接口类型的指针
在将接口进行上行转换时检查接口id,确保插件的有效性
父类指针指向子类对象,通过检查插件子类对象中存储的父类id,来判断子类对象是否有效
Q_INTERFACES(LooseStorageInterface)
Q_PLUGIN_METADATA(IID "LooseStorage_Management_plugin")
子类中interface检查接口的有效性
plugin_metadata提供插件的数据和实例,元对象获取功能
声明当前抽象接口,设置抽象接口的id并提供获取功能,和声明的id进行校验,确保插件的有效性
父类抽象接口
class LooseStorageInterface
{
public:
LooseStorageInterface();
virtual ~LooseStorageInterface(){}
virtual QWidget *GetLooseStoragePanel(QWidget *parent = nullptr) = 0;
virtual QWidget *SetLooseStoragePanel(QSqlDatabase m_db,QWidget *parent = nullptr) = 0;
virtual QString get_name() const =0;
signals:
public slots:
};
Q_DECLARE_INTERFACE(LooseStorageInterface,"LooseStorageInterface.plugins.pluginInterface")
通过qt的元对象系统,插件在声明时将提供的数据插入到当前静态元对象中,通过对静态元对象的相关操作来实现对插件的管理
加载和卸载动态库使用的是c++原生loadlibrary和freelibrary
主程框架中的对外提供统一的抽象接口,qt插件继承自抽象接口,并实现自身的业务需求,主程框架通过调用统一的接口对象来实现对具体插件的调用,插件的作用分担主程框架的业务逻辑及业务需求,主程框架实现业务骨干和核心调度,qt只提供了基础的调度功能,具体的调度逻辑需要我们根据具体业务来实现。
不同模块间通信:
插件先将消息传输到插件管理器中,再由插件管理器的消息调度传给其他插件。
主程序创建插件管理器,调用加载所有插件方法,在由插件管理器去加载初始化所有插件,插件在声明时将数据和其他信息加载到静态元数据中。
如何创建插件?
1.定义一个接口类,使用Q_DECLARE_INTERFACE声明当前为抽象接口和接口IID
2.定义一个插件类,继承QObject和插件想要提供的接口,使用Q_INTERFACES接口ID进行判断,确保接口的有效性,使用Q_PLUGIN_METADATA(IID "Login_Interface_plugin")提供插件的数据和实例
3.使用QPluginLoder去加载初始化所有插件
4.使用QObject_cast()测试插件是否实现了给定接口
//是 qmake 的条件判断语句,用于检查当前是否处于 debug 模式
//无论是 debug 还是 release 模式,最终目标目录都会被设置为 bin/plugins 目录。
//这些目标文件包括可执行文件、静态库文件、动态链接库文件等。
CONFIG(debug,debug|release) {
DESTDIR = $$PWD/../bin/plugins
}else {
DESTDIR = $$PWD/../bin/plugins
}
//你的项目将会生成一个可供 Qt 插件机制使用的插件库
CONFIG += plugin
9.QT中的MVC架构理解
第一步,用户通过与View进行交互,例如编辑单元格、拖放操作等,触发用户操作事件。
第二步,View将用户的操作事件传递给Delegate进行处理。Delegate根据需要对数据进行修改、验证或其他特定操作。
第三步,Delegate将更新后的数据传递给Model进行存储和更新。
第四步,Model通知View数据的变化,View重新获取更新后的数据并刷新显示。
Qt本质上实现的是MV结构,没有Control,中间加了Delegate代理,是把数据的存储方式和视图的展示方式,给解耦了
1. QTableWidget是QTableView的子类,主要的区别是QTableView可以使用自定义的数据模型来显示内容(也就是先要通过setModel来绑定数据源),
而QTableWidget则只能使用标准的数据模型,并且其单元格数据是QTableWidgetItem的对象来实现的(也就是不需要数据源,将逐个单元格内的信息填好即可)。
MyComboDelegate *ComDelegate = new MyComboDelegate;
ui->tableWidget->setItemDelegateForColumn(4,ComDelegate); //设置表格的第四列为自定义代理
- 模型(Model)是用来存储临时对象,数据存储在类、文件和数据库中
- 视图(View)是模型的用户界面,用来显示数据;视图可以通过模型提供的接口来获取数据。模型通常会提供一些方法,如rowCount()、columnCount()和data()等,供视图调用以获取相应的数据。
控制(Controller)定义了用户界面对用户输入的反应方式。- 委托(Delegate)为指定索引的数据,提供一种自定义的展示方式与编辑方式。代理可以通过重写或实现特定的方法
为什么使用代理?
delegate的作用是:为指定索引的数据,提供一种自定义的展示方式与编辑方式,仅此而已。
1.如果我们使用QTableView来作为表格控件,显示数据,但是突然有需求需要在里面添加一些交互的控件,例如:按钮、进度条之类的,
可以使用QTableWidget中的setCellWidget方法添加控件,但是这个不支持模型-视图框架,无法使用自定义的数据模型。
2.但是这种在表格中插入控件的方式,这个控件是一直显示的,效果不好
代理如何实现?
通常委托都继承QStyledItemDelegate和QItemDelegate这个两个类。
- 代理提供了访问模型和视图的接口,当要实现复杂功能的显示时(例如表格中显示下拉框、按钮、进度条等),只靠模型和视图,不能实现;需要借助代理来实现。代理可以通过创建编辑器(编辑器可以是下拉框、按钮或者进度条等),并实现代理提供的虚函数,来实现这些功能。
CreateEditor() 用于创建编辑模型数据的组件,例如(QSpinBox组件)
SetEditorData() 将数据从模型设置到编辑器中
SetModelData() 将编辑器中的数据保存到模型中。
UpdateEditorGeometry() 更新编辑器的位置和大小
paint() 函数:用于绘制代理项的外观。
模型作用?
模型或数据模型(Model)与实际数据通信,并为视图组件提供数据接口。
-
rowCount(const QModelIndex &parent) const:
- 这个函数用于返回模型中指定父节点下的行数(即子项数量)。
-
columnCount(const QModelIndex &parent) const:
- 这个函数用于返回模型中每行的列数(即每个数据项的字段数量)。
-
data(const QModelIndex &index, int role) const:
- 这个函数用于返回模型中指定索引位置的数据。
-
flags(const QModelIndex &index) const:
- 这个函数用于返回指定索引位置的项目的标志,包括是否可编辑、是否可选择等。
-
headerData(int section, Qt::Orientation orientation, int role) const:
- 这个函数用于返回表头(Header)的数据,包括行头和列头。
所有 item model 都基于 QAbstractItemModel 类。这个类定义了一个接口,View和delegate 使用该接口访问数据。
数据本身并不一定要存储在model 中;它可以保存在由单独的类、文件、数据库或其他应用程序组件提供的数据结构或存储库中。 这就是分离数据。model相当于展示数据。
model 目的有点像做了一个中间层来联系和界面的关系。一个模型可以在多个视图中使用
自定义model示例,优化性能和内存
一般情况下满足需求了,不过有时候需要一些定制功能,或者是大量数据下对性能和开销比较注重,觉得自带的model无用功能太多效率比较低,这时候自定义model就比较适合了。
在 Qt 中,有以下几种常用的 Model 类:
1. QAbstractItemModel:是所有 Model 类的基类,定义了访问 model 数据的标准接口。
2. QStandardItemModel:是 QAbstractItemModel 的子类,提供了一个基于项的可编辑的 Model,每个项都可以包含一个文本、一个图标和一个数据项。
3. QStringListModel:是 QAbstractListModel 的子类,用于存储字符串列表作为 Model 数据。
4. QSqlTableModel:是 QAbstractTableModel 的子类,用于与数据库中的单个表进行交互。
5. QFileSystemModel:是 QAbstractItemModel 的子类,用于显示本地文件系统的目录和文件的 Model。
6. QSortFilterProxyModel:是 QAbstractProxyModel 的子类,提供了对源 Model 进行排序、筛选的功能。
7. QAbstractListModel:是 QAbstractItemModel 的子类,用于存储简单的可扩展的列表数据。
8. QAbstractTableModel:是 QAbstractItemModel 的子类,用于存储表格数据。
9. QStandardItem:是 QAbstractItemModel 的一个辅助类,用于在 QStandardItemModel 中存储数据项。
QSqlQueryModel、QSqlTableModel和QSqlRelationalTableModel组织、封装数据库数据,并提供数据的访问接口。
- 虽然QSqlTableModel继承自QSqlQueryModel类,但其灵活性比QSqlQueryModel要多很多。
- QSqlTableModel能对数据进行操作,并且操作QSqlTableModel对象中的数据,将会对对应的数据表进行修改;
QTableView可以使用自定义的数据模型,用setModel方法调用,
自定义sqlmodel继承QSqlQueryModel设置数据,返回模型索引QModelIndex getSelectIndex() const;
QString querySQL=QString(R"(select NULL,"MC"||' '||"GGBM" from "REPAIR"."WXBZ_JCXX_GJXX" where ("SCBS" <> 1 or "SCBS" is null or "SCBS"='') )");
qDebug() << querySQL;
materialNameTable = new MaterialNameTable(db,querySQL,2,ColumnFunctions,10000,28,colWidthList,this);
materialNameTable->setGeometry(44,174,712,136);
materialNameTable->show();
int delegateColIndex = 0;
materialNameTable->SetColumnDelegate(delegateColIndex,0,1);
if(delegateType == 1){//复选框代理
MaterialCheckBoxDelegate *columnDelegate = new MaterialCheckBoxDelegate(queryModel,columnIndex,dValueIndex,startX,this);
connect(columnDelegate,SIGNAL(ClickEvent(int,QString,QSqlRecord)),this,SLOT(Slot_ClickEvent(int,QString,QSqlRecord)));
connect(this,SIGNAL(mouseMoveSig(QModelIndex&,QPoint)),columnDelegate,SLOT(MouseMoveEvent(QModelIndex&,QPoint)));
connect(this,SIGNAL(mouseReleaseSig(QModelIndex&,QPoint)),columnDelegate,SLOT(MouseRelaseEvent(QModelIndex&,QPoint)));
connect(this,SIGNAL(mouseClickSig(QModelIndex&,QPoint)),columnDelegate,SLOT(MouseClickEvent(QModelIndex&,QPoint)));
queryTable->setItemDelegateForColumn(columnIndex,columnDelegate);
//queryTable->setColumnHidden(columnIndex,true);
deledagateList.insert(columnIndex,columnDelegate);
}
MaterialNameSqlQueryModel *queryModel;
QSqlQueryModel的void setQuery() //设置一个QSqlQuery对象,获取数据
QSqlRecord record = queryModel->record(rowIndex);通过record方法获取模型某行的数据
把修改后的数据通过sql更新到数据库,数据模型在重新调用QSqlQueryModel的setQuery方法,自动更新到tableview中
10.QT属性系统
inventory_btn_freeze_r->setProperty("inventory","on"); //设置属性和值
inventory_btn_freeze_r->setStyle(QApplication::style());//调用
//样式表中设置样式
QWidget[temp='freeze']#CabinetCabTopWidget{
background-color: #43bcd8;
}
线程安全:是指在多线程环境中,当多个线程同时访问共享资源时,不会发生不正确的行为或者数据损坏的情况。
12.使用过的控件有哪些?
QLabel、QPushButton、QWidget、QDialog、QCheckbox、QTabWidget(标签组件)、布局器、QtableWidget、QComboBox、QCharts
dialog:模态是阻塞
show()
方法是非模态的,当调用show()
方法后,程序会继续执行,并且用户可以与对话框以及应用程序的其他部分进行交互。exec()
方法是模态的,调用它会阻塞程序的执行,- 当调用
exec()
方法后,程序会进入事件循环,直到用户关闭对话框才会继续执行后续代码。
QCharts