目 录
一、元对象系统简介
二、信号和槽
三、类型信息
四、动态设置属性
一、元对象系统简介
QT中的元对象系统Q_OBJECT并不是C++标准代码,因此在使用时需要QT的MOC(元对象编译器)进行预处理,MOC会在编译时期读取C++代码中的特定宏(如Q_OBJECT),再由标准的C++编译器进行重新编译。
Q_OBJECT的使用:必须要在类中定义元对象系统Q_OBJECT 宏才能使用(在类定义时,如果没有指定public或者private,则默认为private(私有))。程序运行时,moc会扫描此类,并生成元对象信息,包括但不限于类名、父类、属性、信号、槽函数等;
Q_OBJECT的特性:
- 类型信息:Qt使用元对象系统来存储关于对象的信息,如类名和父类。
- 属性系统:支持动态的属性机制,允许在运行时查询和修改对象的属性。
- 信号和槽的动态连接:元对象系统允许在运行时创建和解除信号与槽之间的连接。
二、信号和槽
(1)信号与槽是对象间进行通信的机制,使用QObject::connect
函数连接信号和槽时,元对象系统会在运行时查找信号(signals)和槽(slots),并建立连接。使用方式如下:定义两个类,名为QtWidgetsApplication3、obj,互相通信。
QtWidgetsApplication3.h
#pragma once
#include <QtWidgets/QMainWindow>
#include "ui_QtWidgetsApplication3.h"
#include <QObject> //包含头文件,以使用信号槽
class QtWidgetsApplication3 : public QMainWindow
{
//启用Qt的元对象系统,允许使用信号和槽等特性
Q_OBJECT //默认私有
public:
QtWidgetsApplication3(QWidget *parent = nullptr);
~QtWidgetsApplication3();
private:
Ui::QtWidgetsApplication3Class ui;
signals: //声明信号
void testsignal();
};
class obj : public QObject //继承QObject使用信号槽
{
//启用Qt的元对象系统,允许使用信号和槽等特性
Q_OBJECT
public: //声明构造函数,接受一个QObject指针作为父对象
obj(QObject* parent = nullptr);
public slots: //声明槽
void testslot();
};
QtWidgetsApplication3.cpp
#include "QtWidgetsApplication3.h"
QtWidgetsApplication3::QtWidgetsApplication3(QWidget *parent)
: QMainWindow(parent)
{
ui.setupUi(this);
obj* qobj = new obj(this); //创建obj的实例
QObject::connect(this,&QtWidgetsApplication3::testsignal, qobj,&obj::testslot); //连接信号和槽
emit testsignal(); //触发信号
}
void obj::testslot()
{ qDebug() << "obj::testslot()"; }
QtWidgetsApplication3::~QtWidgetsApplication3()
{}
obj::obj(QObject* parent)
{}
运行效果:
(2)信号和槽的五种写法与使用方式
(3)信号和槽之间的关系:
1、信号的参数类型必须与槽函数参数的类型相对应
signals: //声明信号
void testsignal(int);
public slots: //声明槽
void testslot(int);
emit testsignal(100); //触发信号
2、信号的参数个数大于等于槽函数的参数个数
signals: //声明信号
void testsignal(int,int);
public slots: //声明槽
void testslot(int);
emit testsignal(100,100); //触发信号
3、信号和槽函数之间的关系是多对多,信号也可以 连接到另一个信号上
signals: //声明信号
void testsignal(int);
public slots: //声明槽
void testslot(int);
public slots: //声明槽
void testslot2(int);
QObject::connect(this,&QtWidgetsApplication3::testsignal, qobj,&obj::testslot); //连接信号和槽
QObject::connect(this,&QtWidgetsApplication3::testsignal, qobj,&obj::testslot2); //连接信号和槽
emit testsignal(100); //触发信号
4、信号和槽的连接机制原理:通过QObject::connect来实现的
QObject::connect(sender, &Sender::signal, receiver, &Receiver::slot);
//在上述代码中,sender对象的signal信号与receiver对象的slot槽函数被连接起来。
5、信号和槽的连接类型:
- 直接连接(Direct Connection):这种连接方式类似于自动连接,但是即使在不同线程中也会立即调用槽函数。需要注意的是,这种方式可能导致竞态条件,因为它绕过了事件队列,所以使用时需要特别小心。
- 队列连接(Queued Connection):在这种模式下,信号的发射不会立即导致槽函数的执行。相反,信号会被放入事件队列中,在下一个事件循环开始时才执行槽函数。这种方式常用于跨线程通信,因为可以避免直接从非GUI线程中调用可能会修改GUI组件的方法。
- 自动连接(Auto Connection):这是默认的连接方式。当信号被发射时,Qt会立即调用相关的槽函数。这种方式适用于大多数情况,因为它是最快的方式,并且不需要额外的线程同步机制。
//直接连接
connect(button, &QPushButton::clicked, this, &MyWidget::handleClick, Qt::DirectConnection);
//队列连接
connect(button, &QPushButton::clicked, this, &MyWidget::handleClick, Qt::QueuedConnection);
//自动连接
connect(button, &QPushButton::clicked, this, &MyWidget::handleClick);
6、信号发射原理
emit mySignal(data);
//在上述代码中,当调用emit mySignal(data);时,所有连接到mySignaal的槽函数都会被调用。
7、槽函数调用过程
当一个信号被发射时,Qt负责按连接顺序调用与该信号连接的所有槽函数。
8、信号发射与线程
- 信号可以安全地跨线程发射,如果信号接收者位于不同线程,Qt会自动将信号的处理放入目标线程的事件循环中,确保线程安全。
- 信号的发射和槽的执行会自动适应线程间的通信机制,通过Qt的事件循环和消息队列机制来实现。
- 线程安全:Qt的跨线程信号和槽机制是线程安全的,开发者无需担心常见的多线程问题,如竞态条件和死锁。
- 自动同步:Qt处理所有线程间的通信细节,确保数据在线程间传递时的完整性和一致性。
7、信号和槽异步调用
当信号和槽位于不同线程时,Qt使用事件循环来实现异步调用。信号的发射将产生一个事件,该事件被放入目标线程的事件队列中。当事件循环处理到这个事件时,与之关联的槽函数被调用。
8、事件队列的角色
事件队列在信号和槽的跨线程通信中起着至关重要的作用。每个线程都有自己的事件队列和事件循环。当一个线程向另一个线程发出信号时,这个信号被封装成一个事件,然后被加入接收线程的事件队列。这确保了即使在高度并发的环境下,槽函数的执行也是线程安全的。
三、类型信息
Q_OBJECT 宏使得类可以通过 QMetaObject获取详细的运行时类型信息。这些信息包括类名、父类、信号、槽、属性等。
QtWidgetsApplication4.h
#pragma once
#include <QtWidgets/QMainWindow>
#include "ui_QtWidgetsApplication4.h"
#include <QMetaObject> //获取元对象
#include <QMetaMethod> //信号与槽
#include <QMetaProperty> //属性
#include <QDebug>
#include <QObject>
class QtWidgetsApplication4 : public QMainWindow
{
Q_OBJECT
public:
QtWidgetsApplication4(QWidget *parent = nullptr);
~QtWidgetsApplication4();
private:
Ui::QtWidgetsApplication4Class ui;
signals:
void mysignal(int); //设置类的信号
private slots:
void myslot(int); //设置类的槽
private:
int myProperty=false;
};
QtWidgetsApplication4.cpp
#include "QtWidgetsApplication4.h"
QtWidgetsApplication4::QtWidgetsApplication4(QWidget *parent)
: QMainWindow(parent)
{
ui.setupUi(this);
//获取元对象
const QMetaObject *metaObject = this->metaObject();
//打印类名
qDebug() << metaObject->className();
//打印所有信号
qDebug() << "Signals:";
for (int i = 0; i < metaObject->methodCount(); i++)
{
QMetaMethod method = metaObject->method(i);
qDebug() << method.name();
}
//打印所有槽
qDebug() << "Slots:";
for (int i = 0; i < metaObject->methodCount(); i++)
{
QMetaMethod method = metaObject->method(i);
if (method.methodType() == QMetaMethod::Slot)
qDebug() << method.name();
}
//打印所有属性
qDebug() << "Properties:";
for (int i = 0; i < metaObject->propertyCount(); i++)
{
QMetaProperty property = metaObject->property(i);
qDebug() << property.name();
}
}
QtWidgetsApplication4::~QtWidgetsApplication4()
{}
void QtWidgetsApplication4::myslot(int)
{}
运行效果:
四、动态设置属性
在QT中,动态属性设置的添加与修改,可以应用于数据绑定、状态管理、主题样式、事件处理等;例如:可以将 UI 控件的属性与模型数据绑定,这样当模型数据发生变化时,UI 控件的属性会自动更新。
QtWidgetsApplication5.cpp
#include "QtWidgetsApplication5.h"
QtWidgetsApplication5::QtWidgetsApplication5(QWidget *parent)
: QMainWindow(parent)
{
ui.setupUi(this);
//获取当前对象的元对象
const QMetaObject *metaObject = this->metaObject();
//打印类名
qDebug() << metaObject->className();
//动态设置属性
this->setProperty("dynamicProperty",QVariant(42));
//动态获取属性
QVariant value = this->property("dynamicProperty");
qDebug() << "dynamicProperty:" << value.toInt();
//检查属性是否存在
QVariant checkValue =this->property("dynamicProperty");
if (checkValue.isValid()) { qDebug() << "Property exists and its value is:" << checkValue.toInt(); }
else { qDebug() << "Property does not exist."; }
//修改属性
this->setProperty("dynamicProperty", QVariant(100));
//获取修改后的属性值
value = this->property("dynamicProperty");
qDebug() << "Modified dynamic property value:" << value.toInt();
//删除属性
this->setProperty("dynamicProperty",QVariant());
checkValue = this->property("dynamicProperty");
if (checkValue.isValid()) {qDebug() << "Property exists and its value is:" << checkValue.toInt();}
else {qDebug() << "Property has been 'deleted'.";}
}
QtWidgetsApplication5::~QtWidgetsApplication5()
{}
QtWidgetsApplication5.h
#pragma once
#include <QtWidgets/QMainWindow>
#include "ui_QtWidgetsApplication5.h"
#include <QMetaObject> //获取元对象
#include <QMetaMethod> //信号与槽
#include <QMetaProperty> //属性
#include <QDebug>
class QtWidgetsApplication5 : public QMainWindow
{
Q_OBJECT
public:
QtWidgetsApplication5(QWidget *parent = nullptr);
~QtWidgetsApplication5();
private:
Ui::QtWidgetsApplication5Class ui;
};
运行效果: