文章目录
- Qt 元对象系统、信号和槽及事件目录
元对象系统
元对象系统基本概念
- 元对象系统是QT对原有c++的一些扩展,主要是为实现信号和槽机制运行时类型信息和动态属
性系统等而引入的,信号与槽的机制是QT的核心机制。 - 使用元对象系统的功能的三个条件
- 该类必须继承自QObject类
- 必须在类声明的私有区域添加 Q_OBJECT 宏,该宏用于启动元对象特性,然后便可使用动态特性、信号和槽等功能了
- 元对象编译器(moc)为每个 QObject 的子类,提供实现了元对象特性所必须的代码
- moc 全称是 Meta-Object Compiler(元对象编译器),它是一个工具(类似于 qmake),该工具读取并分析 C++源文件,若发现一个或多个包含了 Q_OBJECT 宏的类的声明,则会生成另外一个包含了 Q_OBJECT 宏实现代码的 C++源文件(该源文件通常名称为 moc_*.cpp) ,这个新的源文件要么被#include 包含到类的源文件中,要么被编译键接到类的实现中(通常是使用的此种方法)。注意:新文件不会“替换”掉旧的文件,而是与原文件一起编译。
元对象系统与反射机制
- reflection 模式(反射模式或反射机制):是指在运行时,能获取任意一个类对象的所有类型信息、属性、成员函数等信息的一种机制
- 元对象:是指用于描述另一个对象结构的对象。使用编程语言具体实现时,其实就是一个类的对象,只不过这个对象专门用于描述另一个对象而已,比如 class B{…}; class A{…B mb;…};假设 mb 是用来描述类 A 创建的对象的,则 mb 就是元对象。
- Qt 具体实现反射机制的方法
- QMetaObject类描述了 QObject 及其派生类对象的所有元信息,该类是 Qt 元对象系统的核心类,因此可以说 QMetaObject 类的对象是 Qt 中的元对象。QMetaObject 类对整个类对象进行描述,比如要获取成员函数的函数名,其代码如下
QMetaMethod qm = metaObject->method(1); //获取索引为 1 的成员函数 qDebug()<<qm.name()<<"\n"; //输出该成员函数的名称。
- 使用 Qt 反射机制的条件
- 需要继承自 QObject 类,并需要在类之中加入 Q_OBJECT 宏。
- 注册成员函数:若希望普通成员函数能够被反射,需要在函数声明之前加入QObject::Q_INVOKABLE 宏。
- 注册成员变量:若希望成员变量能被反射,需要使用 Q_PROPERTY 宏。
- Qt 反射机制实现原理简述
- Q_OBJECT 宏展开之后有一个虚拟成员函数 meteObject(),该函数会返回一个指向QMetaObject 类型的指针
- Qt 的 moc 会完成以下工作,为 Q_OBJECT 宏展开后所声明的成员函数的成生实现代码,识别Qt中特殊的关键字及宏,比如识别出Q_PROPERTY宏、Q_INVOKABLE宏、slot、signals等
qobject_cast 函数
DestType* qobject_cast<DestType*>(QObject *p);
- 该函数类似于 C++中的 dynamic_cast,但执行速度比 dynamic_cast 更快,且不需要C++的 RTTI 的支持,但 qobject_cast 仅适用于 QObject 及其派生类。
- 主要作用是把源类型 QObject 转换为尖括号中的目标类型 DesType(或者子类型),并返回指向目标类型的指针,若转换失败,则返回 0。或者说源类型 QObject 属于目标类型 DestType(或其子类型),则返回指向目标类型的指针,否则返回 0。
- 使用 qobject_cast 的条件:目标类型 DestType 必须继承(直接或间接)自 QObject,并使用 Q_OBJECT 宏。
使用反射机制获取类对象的成员函数的信息
QMetaMethon 类
- 用于描述对象的成员函数,可使用该类的成员函数获取对象成员函数的信息。
enum MethodType{Method, Signal, Slot, Constructor}
此枚举用于描述函数的类型,即:普通成员函数(Method)、信号(Signal)、槽(Slot)、构造函数(Constructor)。
enum Access{Private, Protected, Public}
此枚举主要用于描述函数的访问级别(私有的、受保护的、公有的)
QByteArray methodSignature() const;
返回函数的签名(qt5.0)
MethodType methodType() const;
返回函数的类型(信号、槽、成员函数、构造函数)
QByteArray name() const;
返回函数的名称(qt5.0)
int parameterCount() const
返回函数的参数数量
QList<QByteArray> parameterNames() const;
返回函数参数名称的列表。
Access access() const;
返回函数的访问级别(私有的、受保护的、公有的)
const char * typeName() const;
返回函数的返回类型的名称。
QMetaObject类
int indexOfMethod(const char* f) const;
返回名为 f 的函数的索引号,否则返回-1。此处应输入正确的函数签名,比如函数形式为 void f(int i,int j);则正确的形式为 xx.indexOfMethod(“f(int,int”)"); 以下形式都不是正确的形式,“f(int i, int j)”、“void f(int, int)”、 “f”、"void f"等。
int indexOfSignal(const char * s) const;
返回信号 s 的索引号,否则返回-1,若指定的函数存在,但不是信号,仍返回-1。
int indexOfConstructor(const char *c) const;
返回构造函数 c 的索引号,否则返回-1
int constructorCount() const ;
返回构造函数的数量。
QMetaMethod constructor(int i)const;
返回指定索引 i 处的构造函数的元数据。
int methodCount() const;
返回函数的数量,包括基类中的函数、信号、槽和普通成员函数。
int methodOffset() const;
返回父类中的所有函数的总和,也就是说返回的值是该类中的第一个成员函数的索引位置。
QMetaMethod method(int i) const;
返回指定索引 i 处的函数的元数据
const char* className() const;
获取类的名称
const QMetaObject* superClass() const;
返回父类的元对象,若没有这样的对象则返回 0。
bool inherits(const QMetaObject* mo) const;
若该类继承自 mo 描述的类型,则返回 true,否则返回 false。类被认为继承自身
属性系统
基于元对象系统,通过信号和插槽提供对象间的通信,不依赖于编译器
使用Q_PROPERTY宏定义的属性,就是静态属性。后期使用setProperty()动态添加的属性,就是动态属性
- 可使用 QObject::property 和 QObject::setProperty 函数进行存取
- 若属性有相关联的存取函数,则可使用存取函数进行存取
- 属性还可通过元对象系统的 QMetaObject 类进行存取
信号与槽
connect第五个参数
枚举 | 值 | 说明 |
---|---|---|
Qt::AutoConnection | 0 | (自动关联,默认值)。若接收者驻留在发射信号的线程中(即信号和槽在同一线程中),则使用 Qt :: DirectConnection,否则,使用 Qt :: QueuedConnection。当信号发射时确定使用哪种关联类型。 |
Qt::DirectConnection | 1 | 直接关联。当信号发射后,立即调用槽。在槽执行完之后,才会执行发射信号之后的代码(即 emit 关键字之后的代码)。该槽在信号线程中执行。 |
Qt::QueuedConnection | 2 | 队列关联。当控制权返回到接收者线程的事件循环后,槽才会被调用,也就是说 emit 关键字后面的代码将立即执行,槽将在稍后执行,该槽在接收者的线程中执行。 |
Qt::BlockingQueuedConnection | 3 | 阻塞队列关联。和 Qt :: QueuedConnection 一样,只是信号线程会一直阻塞,直到槽返回。如果接收者驻留在信号线程中,则不能使用此连接,否则应用程序将会死锁。 |
Qt::UniqueConnection | 0x80 | 唯一关联。这是一个标志,可使用按位或与上述任何连接类型组合。当设置 Qt :: UniqueConnection 时,则只有在不重复的情况下才会进行连接,如果已经存在重复连接(即,相同的信号指向同一对象上的完全相同的槽),则连接将失败,此时将返回无效的 QMetaObject::Connection |
事件的传递(或分发)及处理
- 基本规则:若事件未被目标对象处理,则把事件传递给其父对象处理,若父对象仍未处理,则再传递给父对象的父对象处理,重复这个过程,直至这个事件被处理或到达顶级对象为止。注意:事件是在对象间传递的,这里是指对象的父子关系,而不是指类的父子关系。
- 在 Qt 中有一个事件循环,该循环负责从可能产生事件的地方捕获各种事件,并把这些事件转换为带有事件信息的对象,然后由 Qt 的事件处理流程分发给需要处理事件的对象来处理事件。
- 通过调用 QCoreApplication::exec()函数启动事件主循环,主循环从事件队列中获取事件,然后创建一个合适的 QEvent 对象或其子类类型的对象来表示该事件,在此步骤中,事件循环首先处理所有发布的事件,直到队列为空,然后处理自发的事件,最后处理在自发事件期间产生的已发布事件。注意:发送的事件不由事件循环处理,该类事件会被直接传递给对象。
- 然后,Qt 会调用 QCoreApplication::notify()函数对事件进行传递(或分发)
- 最后,QObject 对象调用 QObject::event()函数接收事件,
- 调用顺序:事件1–>exec()–>notify()–>QObject::event()–>keyPressEvent()
- 事件和信号
- 他们两个是不同的概念,不要弄混淆。信号是由对象产生的,而事件则不一定是由对象产生的(比如由鼠标产生的事件),事件通常来自底层的窗口系统,但也可以手动发送自定义的事件,可见信号和事件的来源是不同的。
- 事件既可以同步使用,也可以异步使用 (取决于调用 sendEvent()还是 postEvents()),而使用信号和槽总是同步的。事件的另一个好处是可以被过滤
- 事件的分类
- 自发事件:这是由窗口系统生成的,这些事件置于系统队列中,并由事件循环一个接一个地处理。
- 发布的事件(Posted events):该类事件由 Qt 或应用程序生成,这些事件由 Qt 排队,并由事件循环处理。
- 发送的事件(Sent events):该类事件由 Qt 或应用程序生成,这些事件直接发送到目标对象,不经过事件循环处理。
事件的接受和忽略
- 使用 QEvent::accept()函数表示接受一个事件,使用 QEvent::ignore()函数表示忽略一个事件。也就是说若调用 accept(),则事件不会传递给父对象,若调用 ignore()则事件会向父对象传递。
- Qt 默认值是 accept (接受事件),但在 QWidget 的默认事件处理函数(比如 keyPressEvent())中,默认值是ignore().
- event()函数中调用 accept()或 ignore()是没有意义的,event()函数通过返回一个 bool 值来告诉调用者是否接受了事件(true 表示接受事件)。
事件过滤器
- 事件过滤器用于拦截传递到目标对象的事件,这样可以实现监视目标对象事件的作用
- Qt 实现事件过滤器的步骤如下
void QObject::installEventFilter (QObject* filterObj)
把 filterObj 对象设置安装(或注册)为事件过滤器,filterObj 也称为过滤器对象。事件过滤器通常在构造函数中进行注册- 在上一步注册的 filterObj 对象,通过调用
bool QObject::eventFilter(QObject* obj, QEvent* e)
来接收拦截到的事件。
也就是说拦截到的事件在 filterObj 对象中的 eventFilter 函数中处理。eventFilter 的第一个参数 obj 指向的是事件本应传递到的目标对象。 - 使用
QObject::removeEventFilter(QObject *obj)
函数可以删除事件过滤器。
- 事件过滤器处理事件的规则
- 若同一对象安装了多个事件过滤器,则最后安装的过滤器首先被激活
- 过滤器对象的 eventFilter()函数可以接受或拒绝拦截到的事件,若该函数返回 false,则表示事件需要作进一步处理,此时事件会被发送到目标对象本身进行处理(注意:这里并未向父对象进行传递),若 evetnFilter()返回 true,则表示停止处理该事件,此时目标对象和后面安装的事件过滤器就无法获得该事件。
- 示例
class A : public QObject
{
public: // 该类的对象用作过滤器对象,使用事件过滤器需继承 QObject
bool eventFilter(QObject *w, QEvent *e)
{
if (e->type() == QEvent::MouseButtonPress)
{
cout << w->objectName().toStdString(); // 验证 w 为事件本应到达的目标对象
cout << "=Ak" << endl;
return 1; // 返回 1 表示该事件不再进一步处理
}
return 0;
} // 返回 0 表示其余事件交还给目标对象处理,本例应返回 0,否则添加了该过滤器的安钮会无法显示
};
class B : public A
{
public: // 继承自类 A
bool eventFilter(QObject *w, QEvent *e)
{
if (e->type() == QEvent::MouseButtonPress)
{
cout << w->objectName().toStdString() << "=Bk" << endl;
return 0;
}
return 0;
}
};
class C : public QWidget
{
public:
void mousePressEvent(QMouseEvent *e) { cout << "Ck" << endl; }
};
class D : public QPushButton
{
public:
void mousePressEvent(QMouseEvent *e) { cout << "DK" << endl; }
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
// 创建对象,注意:本例父对象应先创建,以避免生命期过早结束
A ma;
B mb;
C mc;
D *pd = new D;
D *pd1 = new D;
pd->setText("AAA");
pd->move(22, 22);
pd1->setText("BBB");
pd1->move(99, 22);
// 设置对象名
ma.setObjectName("ma");
mb.setObjectName("mb");
mc.setObjectName("mc");
pd->setObjectName("pd");
pd1->setObjectName("pd1");
// 设置父对象
pd->setParent(&mc);
pd1->setParent(&mc);
mb.setParent(&ma); // ①
// 注册过滤器对象
pd->installEventFilter(&mb); // ②
pd1->installEventFilter(&ma); // ③
mc.resize(333, 222);
mc.show();
a.exec();
return 0;
}
//先处理事件过滤器内容,在处理事件
- 安装多个事件过滤器后,后安装的先处理
- eventFilter 函数返回 0 并不会使事件返回给目标对象,而是传递给下一个过滤器对象,当所有过滤器对象都不处理该事件时才会传递给目标对象。
自定义事件与事件的发送
static void QCoreApplication::postEvent (QObject* receiver, QEvent* event, int priority=Qt::NormalEventPriority);
static bool QCoreApplication::sendEvent(QObject* receiver, QEvent* event)
- receiver:指向接收事件的对象,event:表示需要发送的事件,priority:表示事件的优先级,高优先级的事件排在队列的前面
- 发送事件(sendEvent)与发布事件(postEvent)
- 发布(post)事件:
- 把事件添加到事件队列中,并立即返回。
- 发布事件必须在堆(比如使用 new)上创建事件,因为事件被发布后,事件队列将获得事件的所有权并自动将其删除。发布事件后再访问该事件是不安全的。
- 发布事件还可以对事件进行合并(或称为压缩),比如在返回事件循环之前连续发布了多个相同的事件,则这多个相同的事件会自动合并为一个单一的事件。可合并的事件有鼠标移动事件等。
- 发送(send)事件
把事件直接发送给接收事件的目标对象。事件发送之后不会被删除,发送的事件通常创建在堆栈上。
- 自定义事件原理
基本原理:事件其实就是使用的一个整数值表示的,因此在创建自定义事件时,只须给事件指定一个整数值即可,在 Qt 中,这个整数值是通过枚举类型 QEvent::Type 定义的,事件的其他信息可以封装在一个自定义的类之中。 - 示例:自定义事件的使用
QEvent::Type t1 = (QEvent::Type)1333;
QEvent e(t1); // 使用 QEvent 的构造函数在堆栈上创建自定义事件
class E : public QEvent
{
public: // 子类化 QEvent 以创建自定义事件
// 方式 1:使用静态成员。
// 使用静态成员主要是为了正确初始化父类部分 QEvent,比如
// E():t2((QEvent::Type)1324),QEvent(t2){},若 t2 不是静态的,则则初始化之后 t2 为 1324,但传递
// 给 QEvent 的 t2 是一个不确定的值,因为按照 C++规则,对父类部分的初始化先于数据成员的初始化。
static QEvent::Type t2; // 注意:不要使用名称 t,因为 QEvent 类之中有一个名称为 t 的成员变量。
E() : QEvent(t2) {}
// 方式 2:使用带一个参数的构造函数
QEvent::Type t3;
explicit E(QEvent::Type t4) : t3(t4), QEvent(t4) {}
};
QEvent::Type E::t2 = (QEvent::Type)1334;
class A : public QWidget
{
public:
bool event(QEvent *e)
{ // 重写 event 函数以处理自定义事件
if (e->type() == t1) // 判断事件类型是否为 t1
{
cout << "AE" << e->type() << ",";
f1((E *)e); // 调用自定义的处理函数处理该事件
return 1;
}
if (e->type() == E::t2)
{
cout << "BE" << e->type() << ",";
f2((QEvent *)e);
return 1;
}
if (e->type() == ((E *)e)->t3)
{
cout << "CE" << e->type() << ",";
f3((E *)e);
return 1;
}
return QWidget::event(e);
} // event 结束
// 以下为处理自定义事件的事件处理函数
void f1(E *e) { cout << "F1" << endl; }
void f2(QEvent *e) { cout << "F2" << endl; }
void f3(E *e) { cout << "F3" << endl; }
}; // 类 A 结束。
int main(int argc, char *argv[])
{
QApplication aa(argc, argv);
A ma;
E me;
E *pe = new E((QEvent::Type)1335);
// 发布或发送事件
aa.sendEvent(&ma, &e);
aa.sendEvent(&ma, &me);
aa.postEvent(&ma, pe);
// aa.postEvent(&ma,&me); //错误,发布的事件 me 必须是在堆上创建的。
ma.resize(333, 222);
ma.show();
aa.exec();
return 0;
}
// 运行程序后,输出结果依次为 AE1333,F1 BE1334,F2 CE1335,F3