【笔记】Qt 元对象系统、信号和槽及事件

  • Qt 元对象系统、信号和槽及事件目录

元对象系统

元对象系统基本概念

  • 元对象系统是QT对原有c++的一些扩展,主要是为实现信号和槽机制运行时类型信息和动态属
    性系统等而引入的,信号与槽的机制是QT的核心机制
  • 使用元对象系统的功能的三个条件
    1. 该类必须继承自QObject类
    2. 必须在类声明的私有区域添加 Q_OBJECT 宏,该宏用于启动元对象特性,然后便可使用动态特性、信号和槽等功能了
    3. 元对象编译器(moc)为每个 QObject 的子类,提供实现了元对象特性所必须的代码
  • moc 全称是 Meta-Object Compiler(元对象编译器),它是一个工具(类似于 qmake),该工具读取并分析 C++源文件,若发现一个或多个包含了 Q_OBJECT 宏的类的声明,则会生成另外一个包含了 Q_OBJECT 宏实现代码的 C++源文件(该源文件通常名称为 moc_*.cpp) ,这个新的源文件要么被#include 包含到类的源文件中,要么被编译键接到类的实现中(通常是使用的此种方法)。注意:新文件不会“替换”掉旧的文件,而是与原文件一起编译。

元对象系统与反射机制

  1. reflection 模式(反射模式或反射机制):是指在运行时,能获取任意一个类对象的所有类型信息、属性、成员函数等信息的一种机制
  2. 元对象:是指用于描述另一个对象结构的对象。使用编程语言具体实现时,其实就是一个类的对象,只不过这个对象专门用于描述另一个对象而已,比如 class B{…}; class A{…B mb;…};假设 mb 是用来描述类 A 创建的对象的,则 mb 就是元对象。
  3. Qt 具体实现反射机制的方法
    • QMetaObject类描述了 QObject 及其派生类对象的所有元信息,该类是 Qt 元对象系统的核心类,因此可以说 QMetaObject 类的对象是 Qt 中的元对象。QMetaObject 类对整个类对象进行描述,比如要获取成员函数的函数名,其代码如下
    QMetaMethod qm = metaObject->method(1); //获取索引为 1 的成员函数
    qDebug()<<qm.name()<<"\n"; //输出该成员函数的名称。
    
  4. 使用 Qt 反射机制的条件
    1. 需要继承自 QObject 类,并需要在类之中加入 Q_OBJECT 宏。
    2. 注册成员函数:若希望普通成员函数能够被反射,需要在函数声明之前加入QObject::Q_INVOKABLE 宏。
    3. 注册成员变量:若希望成员变量能被反射,需要使用 Q_PROPERTY 宏。
  5. Qt 反射机制实现原理简述
    1. Q_OBJECT 宏展开之后有一个虚拟成员函数 meteObject(),该函数会返回一个指向QMetaObject 类型的指针
    2. 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::AutoConnection0(自动关联,默认值)。若接收者驻留在发射信号的线程中(即信号和槽在同一线程中),则使用 Qt :: DirectConnection,否则,使用 Qt :: QueuedConnection。当信号发射时确定使用哪种关联类型。
Qt::DirectConnection1直接关联。当信号发射后,立即调用槽。在槽执行完之后,才会执行发射信号之后的代码(即 emit 关键字之后的代码)。该槽在信号线程中执行。
Qt::QueuedConnection2队列关联。当控制权返回到接收者线程的事件循环后,槽才会被调用,也就是说 emit 关键字后面的代码将立即执行,槽将在稍后执行,该槽在接收者的线程中执行。
Qt::BlockingQueuedConnection3阻塞队列关联。和 Qt :: QueuedConnection 一样,只是信号线程会一直阻塞,直到槽返回。如果接收者驻留在信号线程中,则不能使用此连接,否则应用程序将会死锁。
Qt::UniqueConnection0x80唯一关联。这是一个标志,可使用按位或与上述任何连接类型组合。当设置 Qt :: UniqueConnection 时,则只有在不重复的情况下才会进行连接,如果已经存在重复连接(即,相同的信号指向同一对象上的完全相同的槽),则连接将失败,此时将返回无效的 QMetaObject::Connection

事件的传递(或分发)及处理

  1. 基本规则:若事件未被目标对象处理,则把事件传递给其父对象处理,若父对象仍未处理,则再传递给父对象的父对象处理,重复这个过程,直至这个事件被处理或到达顶级对象为止。注意:事件是在对象间传递的,这里是指对象的父子关系,而不是指类的父子关系。
  2. 在 Qt 中有一个事件循环,该循环负责从可能产生事件的地方捕获各种事件,并把这些事件转换为带有事件信息的对象,然后由 Qt 的事件处理流程分发给需要处理事件的对象来处理事件。
  3. 通过调用 QCoreApplication::exec()函数启动事件主循环,主循环从事件队列中获取事件,然后创建一个合适的 QEvent 对象或其子类类型的对象来表示该事件,在此步骤中,事件循环首先处理所有发布的事件,直到队列为空,然后处理自发的事件,最后处理在自发事件期间产生的已发布事件。注意:发送的事件不由事件循环处理,该类事件会被直接传递给对象。
  4. 然后,Qt 会调用 QCoreApplication::notify()函数对事件进行传递(或分发)
  5. 最后,QObject 对象调用 QObject::event()函数接收事件,
  • 调用顺序:事件1–>exec()–>notify()–>QObject::event()–>keyPressEvent()
  • 事件和信号
    1. 他们两个是不同的概念,不要弄混淆。信号是由对象产生的,而事件则不一定是由对象产生的(比如由鼠标产生的事件),事件通常来自底层的窗口系统,但也可以手动发送自定义的事件,可见信号和事件的来源是不同的。
    2. 事件既可以同步使用,也可以异步使用 (取决于调用 sendEvent()还是 postEvents()),而使用信号和槽总是同步的。事件的另一个好处是可以被过滤
  • 事件的分类
    1. 自发事件:这是由窗口系统生成的,这些事件置于系统队列中,并由事件循环一个接一个地处理。
    2. 发布的事件(Posted events):该类事件由 Qt 或应用程序生成,这些事件由 Qt 排队,并由事件循环处理。
    3. 发送的事件(Sent events):该类事件由 Qt 或应用程序生成,这些事件直接发送到目标对象,不经过事件循环处理。

事件的接受和忽略

  1. 使用 QEvent::accept()函数表示接受一个事件,使用 QEvent::ignore()函数表示忽略一个事件。也就是说若调用 accept(),则事件不会传递给父对象,若调用 ignore()则事件会向父对象传递。
  2. Qt 默认值是 accept (接受事件),但在 QWidget 的默认事件处理函数(比如 keyPressEvent())中,默认值是ignore().
  3. event()函数中调用 accept()或 ignore()是没有意义的,event()函数通过返回一个 bool 值来告诉调用者是否接受了事件(true 表示接受事件)。

事件过滤器

  • 事件过滤器用于拦截传递到目标对象的事件,这样可以实现监视目标对象事件的作用
  • Qt 实现事件过滤器的步骤如下
    1. void QObject::installEventFilter (QObject* filterObj)
      把 filterObj 对象设置安装(或注册)为事件过滤器,filterObj 也称为过滤器对象。事件过滤器通常在构造函数中进行注册
    2. 在上一步注册的 filterObj 对象,通过调用bool QObject::eventFilter(QObject* obj, QEvent* e)来接收拦截到的事件。
      也就是说拦截到的事件在 filterObj 对象中的 eventFilter 函数中处理。eventFilter 的第一个参数 obj 指向的是事件本应传递到的目标对象。
    3. 使用 QObject::removeEventFilter(QObject *obj)函数可以删除事件过滤器。
  • 事件过滤器处理事件的规则
    1. 若同一对象安装了多个事件过滤器,则最后安装的过滤器首先被激活
    2. 过滤器对象的 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)事件:
    1. 把事件添加到事件队列中,并立即返回。
    2. 发布事件必须在堆(比如使用 new)上创建事件,因为事件被发布后,事件队列将获得事件的所有权并自动将其删除。发布事件后再访问该事件是不安全的。
    3. 发布事件还可以对事件进行合并(或称为压缩),比如在返回事件循环之前连续发布了多个相同的事件,则这多个相同的事件会自动合并为一个单一的事件。可合并的事件有鼠标移动事件等。
    • 发送(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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值