首页 > 其他分享 >Qt元对象和属性机制

Qt元对象和属性机制

时间:2024-04-20 21:58:37浏览次数:14  
标签:Widget Qt 函数 OBJECT focus QObject 机制 属性

Qt元对象和属性机制

Qt是很早期的库,当时c++连标准库都不完善,如果Qt在c++14以后编写,绝对不会搞这么复杂

概述

Qt的元对象系统提供了3个重要的特性:

  • 信号和槽机制:实现各 QObject 组件之间的通信
  • 实时类型信息:通过运行时使用的类型,执行不同的函数,复用接口
  • 动态属性系统:存储类的相关信息,用于在运行时识别类对象的信息

其中后两者可以认为是一种反射的实现。

使用元对象系统需要满足三个条件:

  • 只有 QObject 派生类才可以使用元对象系统特性。
  • 在类声明前使用 Q_OBJECT 宏来开启元对象功能。
  • 使用MOC工具为每个 QObject 派生类提供实现代码。

QObject

QObject 类是所有需要利用元对象系统的对象的基类。而 Q_OBJECT 宏则是一个加在继承自 QObject 的类的定义部分的宏,用于启用元对象系统,会被MOC处理。

尽管 Q_OBJECT 宏提供了许多便利,但要发挥其全部功能,类必须继承自 QObject。这是因为许多由 Q_OBJECT 宏启用的特性都依赖于 QObject 类的基础设施。如果一个类包含了 Q_OBJECT 宏却不继承自 QObject,这将导致编译错误。反之则如果一个类继承了 QObject 而不启用 Q_OBJECT ,则获取其元对象信息会得到其最近的启用了元对象机制的对象树父对象的信息,显然这不是我们的本意。

因此强烈建议所有 QObject 的子类都启用 Q_OBJECT。启用元对象特性的类将会获得以下能力:

  • 能使用信号与槽

  • 启用事件机制

  • 能挂载在对象树上

    QObject::setParent()
    QObject::findChild()
    QObject::findChildren()
    
  • 提供多语言支持

    QObject::tr()
    QObject::trUtf8()
    
  • 提供反射机制

    QObject::inherits(); // 返回继承信息,对象是否是 QObject 继承树中一个类的实例
    QObject::objectName(); // 唯一的对象名
    
    QOject::setProperty() 和 QObject::property(); // 通过名字动态设置和动态获取对象属性
    
    QObject::metaObject(); // 返回与该类绑定的 meta-object 对象
    QMetaObject::className(); // 在运行时以字符串的形式返回类的名字(无需RTTI)
    QMetaObject::newInstance(); // 构造该类的一个新实例
    
    qobject_cast<类A>(类B); // 基于元对象系统实现的dynamic_cast,要求A和B都是QObject的子类,效率比dynamic_cast高
    

MOC

元对象编译器(Meta-Object Compiler, MOC)为每个 QObject 子对象自动生成必要的代码来实现元对象系统的特性,工作流程可以分为以下几个步骤:

  1. 扫描:MOC 扫描源代码,寻找 Q_OBJECT 宏和其他 Qt 特有的宏 Q_XXX
  2. 生成:对于每一个包含 Q_OBJECT 宏的类,MOC 会为其生成一个额外的 C++ 源文件 moc_xxxx.cpp 。这个文件包含了每个类实现 Qt 元对象特性所需的元数据和辅助代码。
  3. 编译:生成的源文件随后被编译器编译,与应用程序的其他部分一起链接。

MOC文件分析

详见《统信UOS应用开发实战教程》P32开始

MOC存在的坑点

声明和实现在同一文件中时

当类的声明和实现都在同一个源文件(.cpp 文件)中时,在这种结构中使用 Q_OBJECT 宏会带来一个问题:由于 MOC 默认只处理头文件,结果是MOC 不会为这些类生成相应的 .moc 文件,从而导致编译错误,因为 Qt 的信号和槽机制依赖于这些 MOC 生成的代码。

为了解决这个问题,开发者需要在源文件的末尾手动包含由 MOC 生成的 moc 文件。这一操作确保了 MOC 生成的代码被正确编译和链接。例如:

// MyClass.cpp
#include <QObject>

class MyClass : public QObject {
    Q_OBJECT
public:
    MyClass() { /* ... */ }
    // ...
};

// MyClass 实现的其他部分

#include "moc_myclass.cpp" // 需要在源文件的末尾包含,以确保在类的定义之后包含 MOC 生成的代码

其他限制

这里的限制主要是指 moc 工具的限制,主要表现在以下几方面:

  • 模板类中不能使用 Q_OBJECT。

    class SomeTemplate<int> : public QFrame
    {
        Q_OBJECT  // WRONG
        ...
    
    signals:
        void mySignal(int);
    };
    
  • 多重继承时 QObject 必须是第一个,且不支持 QObject 的虚继承。

    // CORRECT
    class SomeClass : public QObject, public OtherClass
    {
        ...
    };
    
  • 函数指针不能作为信号或槽的参数,可以用 typedef 简化函数指针,建议用继承或虚函数代替函数指针。

    // WRONG
    class SomeClass : public QObject
    {
        Q_OBJECT
    
    public slots:
        void apply(void (*apply)(List *, void *), char *);
    };
    
    // CORRECT
    typedef void (*ApplyFunction)(List *, void *);
    
    class SomeClass : public QObject
    {
        Q_OBJECT
    
    public slots:
        void apply(ApplyFunction, char *);
    };
    
  • 枚举类型或 Typedefs 作为信号和槽的参数时必须是完全限定名称。原因是 QObject::connect() 检测参数签名时,是比较的数据类型的字面意思。例如,Alignment 和 Qt::Alignment 会当作两种不同的参数类型。

    class MyClass : public QObject
    {
        Q_OBJECT
    
        enum Error {
            ConnectionRefused,
            RemoteHostClosed,
            UnknownError
        };
    
    signals:
        void stateChanged(MyClass::Error error);
    };
    
  • 内嵌的类不能有信号与槽。

    class A
    {
    public:
        class B
        {
            Q_OBJECT
    
        public slots:  // WRONG
            void b();
        };
    };
    
  • 信号槽的返回类型不能是引用。

  • 在类的 signals 和 slots 代码块中,只能是信号和槽函数的声明,而不能是其他的声明。

Property

利用元对象系统所提供的动态类型信息,Qt还构建了一套强大的属性系统。

属性系统是基于元对象系统的,因此必须在 QObject 和启用了 Q_OBJECT 宏的类中才能用

属性是做什么的

在C++中是没有属性概念的,只有成员变量。因为面向对象的思想是抽象封装,属性是类给外部展示的特性。而成员变量属于类的内部信息,直接暴漏出去就破坏了封装性,因为使用者可以对类特性进行直接修改。而属性将取值、赋值的细节进行了封装,外部只能使用它而不能控制它。

而 Qt 提供的这个属性系统,作用就是把类的信息暴露出来成为通用的大家都认识的信息,从而能暴露给 QML 之类的其他语言处理。

通常在界面插件开发、QML中使用属性系统,例如在Qt Creator的设计界面中,控件都是以属性的方式暴露给外部使用者。

img

如何使用属性

对于属性,常用的操作无非就是读、写、将成员变量导出为属性值、关联信号等。

声明一个属性并赋予读写操作

在 QObject 及其子类中用 Q_PROPERTY 宏声明。它是定义在 QObject 类中的宏,所以只有 QObject 类或者继承自它的类才能使用。

// Q_PPROPERTY 宏
Q_PROPERTY(type name
           (READ getFunction [WRITE setFunction] |
            MEMBER memberName [(READ getFunction | WRITE setFunction)])
           [RESET resetFunction]
           [NOTIFY notifySignal]
           [REVISION int]
           [DESIGNABLE bool]
           [SCRIPTABLE bool]
           [STORED bool]
           [USER bool]
           [CONSTANT]
           [FINAL])
参数 对参数的理解
READ 指定读取属性值的函数,最好是一个 const 函数,函数返回值是该属性的类型或者该属性类型的引用。在没有指定 MEMBER 时,READ 必须指定!
WRITE 指定修改属性值的函数,函数参数有且仅有一个,参数类型必须是属性类型或者该属性类型的指针或引用,无返回值(或者说返回 void 值)。可选参数
MEMBER 导出成员变量为属性值,使得成员变量不需要读函数和写函数的支持也能读写。读写用的是QObject 的函数:property() 和 setProperty()。不能与 READ/WRITE 同时使用
RESET 重置属性值为某个默认值,该函数是一个没有参数且返回值是 void 的函数。可选参数!
NOTIFY 定义一个信号,当属性值已经变化后,Qt 会发出这个信号。当存在参数为 MEMBER 时,这个信号应该不带参数或者带一个参数,带一个参数时参数为属性的新值。可选参数!
REVISION 定义 API 的版本号,使得读/写接口和信号只能在特定的版本中使用。默认值是 0,可选参数!
DESIGNABLE 指定 Qt Designer 中是否能编辑这个属性。默认值是 true,可选参数!
SCRIPTABLE 指定 QtScript 是否可以访问这个属性。默认值是 true,可选参数!
STORED 指定属性是独立属性还是依赖于值其他属性,比如 QWidget::minimumWidth() 依赖于 QWidget::minimumSize()。默认值是 true,可选参数!
USER 指定类的这个属性为用户只读或者可编辑,一个类中只能定义一个 USER 属性。默认值是 false,可选参数!
CONSTANT 属性指定了 CONSTANT 即成为了一个常量属性。类的不同对象这个属性值必须有不同的值。常量属性没有 write 方法和信号。
FINAL 属性指定了 FIANL 将不能被派生类重写。

READ、WRITE、ERSET 指定的函数可以被继承,也能被定义为虚函数。当被多重继承时,取第一个父类的值。


比如 QWidget 这个类,其中使用属性系统的代码如下所示:

Q_PROPERTY(QCursor cursor READ cursor WRITE setCursor RESET unsetCursor)

上面这一行代码就声明了一个 cursor 属性,指明了它是 QCursor 类型的,而且指明了需要用自己的 cursor() 函数来读取这个属性值,指明了用自己的 setCursor() 函数来修改属性值,还指明了用自己的 unsetCursor() 函数进行默认值设置。

指定读取属性值的getter

使用 READ 关键字:

class Widget : public QObject
{
    Q_PROPERTY(bool focus READ hasFocus)
    Q_OBJECT
public:
    bool hasFocus() const;
}

上述代码指定getter为 hasFocus() 函数

指定修改属性值的setter

使用 WRITE 关键字:

class Widget : public QObject
{
    Q_PROPERTY(bool focus WRITE setFocus)
    Q_OBJECT
public:
    bool hasFocus() const;
    void setFocus(bool on);
}

上述代码指定setter为 setFocus() 函数。注意 setter 函数返回值必须为 void

将类的变量导出为一个属性

注意上述的属性值 cursor 可不是 QWidget 的一个成员变量,要想将 QWidget 类中的某个变量导出成为一个属性值,应该用 MEMBER 关键字将变量注册到 Qt 的属性系统。如下:

   Q_PROPERTY(bool focus MEMBER m_focus NOTIFY focusChanged)
   ... 
   bool hasFocus() const;
   void setFocus(bool on);
signals:
   void focusChanged();
private:
   bool m_focus;

上面的代码把 QWidget 类中的 m_focus 成员变量导出为一个属性值,并且起了个新名字“focus”。那么外界所能看到的或者说只认可的属性值只有 focus 而不是 m_focus。

同时,MEMBER 关键字会自动绑定代码中读写 m_focus 的代码,同时使得 focus 是一个可读可写的属性。注意此时不能再设置 READ/WRITE ,因为可读可写特性只能赋予一次。

给属性值设置关联的信号

如果我们希望某个属性值变化时能发射出信号,要使用 NOTIFY 关键字:

实际操作一下读/写属性值?

上文我们创建了 QObject 的子类 Widget,并且指定了修改 focus 属性值的函数,现在我们创建 Widget 类的一个对象 w 来看看实际代码中如何写。

由于赋予属性值读/写有两种办法(方法一是用 READ、WRITE;方法二是 MEMBER ),那么实际使用中针对这两种方式使用略有不同。

如果是用 READ、WRITE,那么直接调用指定的函数即可,如:

Widget *w = new Widget;
w->setFocus(true);

如果是用 MEMBER,那么用 QObject 的 property() 和 setProperty() 两个函数,如:

Widget *w = new Widget;
w->property("focus");
w->setProperty("focus", true);
两种方法哪个好?

当然是 WRITE。它的效率跟高、速度更快,而且在编译阶段就可以进行类型检查。缺点就是还没运行前你就得了解这个类是有 setFocus() 这个函数。而采用 MEMBER 方式时,我们不需要知道这个类有啥函数、有啥变量,只需要知道这个类有一个叫“focus”的属性值就可以了。

获取类中的所有属性

那既然 MEMBER 的好处是只需要知道这个类有一个叫 focus 的属性值,再极端点,我连这个类有啥属性值都不知道时怎么办?Qt 的元对象系统是如此的强大,已经为你准备好了相关的函数了。那就是 QMetaObjectQMetaProperties。下列代码输出了 Widget 类中所有的属性名称和属性值:

Widget *w = new Widget;

const QMetaObject *metaobject = w->metaObject();
int count = metaobject->propertyCount();

for (int i = 0; i < count; ++i) {
    QMetaProperty metaproperty = metaobject->property(i);
    const char *name = metaproperty.name();
    QVariant value = w->property(name);
    ...
} 

在程序运行过程中动态添加属性

QObject::setProperty() 可以在运行时给实例添加属性值。

  • 属性已经存在于对象中

    给的属性值类型正确,这个值就被添加到对象中了,函数返回 true。否则属性值不改变,函数返回 false。

  • 类中没有这个属性

    自动在对象中添加新属性,并赋值,但是函数返回 false。

动态属性只是添加到 QObject 中,而不是 QMateObject 中,故无法通过 QMetaProperties 方法获取到。

移除动态属性可以通过给 setProperty 传入一个空的 QVariant 参数

如何自定义属性类型

自定义的属性类型,需要用 Q_DECLARE_METATYPE 宏进行注册,这样就可以存储在QVariant中了。

如何给类添加额外的属性信息

除了正规常用的属性外,我们还可以用 Q_CLASSINFO 宏给类添加额外的属性信息,语法就是“键值-对”形式。例如:

Q_CLASSINFO("Version", "3.0.0")

那么在程序运行的过程中,随时都可以调用 QMetaObject::classInfo() 函数来获取这些额外属性信息。

完整的示例

上文讲解的 Widget 类由于代码分散在各处,可能对一个类如何操作属性值没有直观的感受,下面用完整的代码来演示属性的一系列操作。

声明代码

class Widget : public QObject
{
    Q_OBJECT
    Q_PROPERTY(bool focus READ hasFocus WRITE setFocus NOTIFY focusChanged)
public:
    Widget(QObject *parent = 0);
    ~Widget();

    bool hasFocus() const
    {
        return m_focus;
    }

    void setFocus(bool on)
    {
        m_focus = on;
    }
signals:
    void focusChanged();

private:
    bool m_focus;
}

解读

我们有一个继承于 QObject 的 Widget 类。我们用 Q_PROPERTY 宏声明了一个属性来跟踪私有变量 m_focus 值,属性名使用 focus,属性类型是个布尔类型。用 READ 指定了读取函数 hasFocus(),用 WRITE 指定了修改函数 setFocus,用 NOTIFY 指定了发射信号 focusChanged()。

使用代码

现在我们有个 Widget 指针和一个 QObject 指针,设置属性值的方法是:

Widget *w = new Widget;
w->setFocus(true);

QObject *o = w;
o->setProperty("focus", true);

标签:Widget,Qt,函数,OBJECT,focus,QObject,机制,属性
From: https://www.cnblogs.com/3to4/p/18148236

相关文章

  • Java 安全基础之 Java 反射机制和 ClassLoader 类加载机制
    目录Java反射机制反射java.lang.RuntimeClassLoader类加载机制URLClassLoaderJava反射机制Java反射(Reflection)是Java非常重要的动态特性。在运行状态中,通过Java的反射机制,我们能够判断一个对象所属的类。了解任意一个类的所有属性和方法。能够调用任意一个对象的任意方......
  • Android系统build阶段签名机制
    https://maoao530.github.io/2017/01/31/android-build-sign/APK签名机制https://maoao530.github.io/2017/01/31/apk-sign/ 本文介绍Android系统build阶段的签名机制。一、系统build阶段签名机制1、系统中有4组key用于build阶段对apk进行签名:MediaPlatformSharedTestk......
  • 复杂属性转换器
    提示自己!具体为案例代码中的[属性Scope]和[类Scope]混淆产生两个不同内容但名字却相同的两个DLL!从而导致不能转换案例链接......
  • java srpint boot 2.2.1 第二部份,锁机制和分页查询 以及统一返回结果格式,
    第二部份,引起锁机制的原理和解决方案: 测试环境搭建第一步先建一个数据库表用于模拟商品购买。CREATETABLEproduct(idINTAUTO_INCREMENTPRIMARYKEY,nameVARCHAR(255)NOTNULL,stockINTNOTNULL,versionINTNOTNULLDEFAULT0);第二步......
  • MQTT协议
    一、MQTT协议简介MQTT(MessageQueuingTelemetryTransport,消息队列遥测传输协议),是一种基于发布/订阅(publish/subscribe)模式的"轻量级"通讯协议,该协议构建于TCP/IP协议上,由IBM在1999年发布。MQTT协议是为工作在低带宽、不可靠的网络的远程传感器和控制设备通讯而设计的协议。二......
  • 基于SkyEye运行Qt:著名应用程序开发框架
    Qt是一个著名的跨平台的C++图形用户界面应用程序开发框架,目前包括QtCreator、QtDesigner等等快速开发工具,还支持2D/3D图形渲染、OpenGL,允许真正的组件编程,是与GTK、MFC、OWL、ATL一样的图形界面库。使用Qt开发的软件可以做到一次开发、任意部署,相同的代码可以在任意支持的平台编......
  • STM32、ESP8266与MQTT连接阿里云物联网的串口通信异常解析
    STM32、ESP8266与MQTT协议连接阿里云物联网平台时常见的串口通信异常介绍在构建物联网应用时,STM32、ESP8266与MQTT协议的结合是实现设备与网络间稳定通信的关键。然而,在连接阿里云物联网平台的过程中,串口通信异常成为了一个常见的挑战。本文将探讨这些异常现象及其可能的原因,并给......
  • 行人属性AI识别/人体结构化属性AI识别算法的原理及应用场景介绍
    行人属性AI识别技术是一种基于人工智能技术的图像识别技术,通过对行人的图像或视频进行处理和分析,提取出其中的结构化信息,如人体姿态、关键点位置、行人属性(性别、年龄、服装等)等。行人结构化数据分析的方法包括姿态估计、关键点检测、行人属性识别等:姿态估计是指根据行人的姿势......
  • v-bind与class,style属性的使用
    class,style是各种dom元素的都具有的原生属性class与:class的区别,直接使用class的话,后面跟的是常量,若使用:class,v-bind:class的话,后面需跟变量,可以实现动态地改变元素的样式如下图代码: 在上述代码中,在button元素中,对class原生属性赋值了,也使用了:class的自定义属性赋值控制......
  • 论TCP协议中的拥塞控制机制与网络稳定性
    TCP协议中的拥塞控制机制与网络稳定性的深度探讨随着互联网的快速发展,网络流量呈现爆炸式增长,网络拥塞问题逐渐凸显。为了维护网络的稳定运行,TCP协议中引入了拥塞控制机制。这一机制的主要目的是防止过多的数据注入网络,从而避免网络拥塞。然而,尽管拥塞控制机制在很大程度上能够减......