【Qt进阶学习】:元对象系统学习(一)

元对象系统学习(一)

一、元对象系统

   元对象是指用于描述另一个对象结构的对象。在Qt中,当我们创建一个QObject子类,并在类的私有区域调用了Q_OBJECT宏时,会由QMetaobject类自动新建一个对象staticMetaObject,这个对象就是用来描述这个子类的元对象。

  1. Qt的元对象系统

    元对象系统提供了内部对象之间通信的信号与槽机制,运行时信息、属性系统、动态属性系统等。元对象系统基于以下三件事。

  • QObject为所有要使用元对象系统的对象提供了一个基类;
  • 必须在类声明的私有区域添加Q_OBJECT宏,该宏用于使能元对象系统,使能后才可以使用信号与槽功、动态属性等功能;
  • 元对象编译器(MOC)为每个QObject子类提供了实现元对象所必须的特性。
  1. MOC

    moc是 Meta-Object Compiler(元对象编译器)的缩写。因为Qt的元对象系统是对C++的扩展,使用通用的编译器去编译带有启用了元对象系统的Qt程序,是编译不了的,因此Qt自己实现了一个编译器MOC。
   该编译器读取C++源文件,如果它发现其中包含一个或多个类的声明中含有Q_OBJECT宏,它就会给含有Q_OBJECT宏的类生成另一个含有元对象代码的C++源文件。这个生成的源文件可以被包含到类的源文件,或者和这个类的实现一起编译和连接。除了提供对象间通讯的信号和槽机制之 外(介绍这个系统的主要原因),QObject中的元对象代码还提供以下特征:

  • metaObject(),返回该类所关联的元对象。
  • className(),在运行的时候以 字符串返回类的名称,不需要C++编译器中的本地运行类型信息(RTTI)的支持。
  • inherits(),返回这个对象是否 是一个继承于QObject继承树中一个特定类的类的实例。
  • tr()和trUtf8() ,这两个函数是用于国际化中的字符串翻译。
  • setProperty()和property(),两个函数是用来通过名称动态设置和获得对象的动态属性。
  • QMetaObject::newInstance(),为类构造一个新的实例。
  1. 反射机制
        reflection 模式(反射模式或反射机制):是指在运行时,能获取任意一个类对象的所有类型信息、属性、成员函数等信息的一种机制。
        在运行阶段,程序可以通过Qt提供的一些类来获取 QObject 派生类对象所属类的名称、父类名称、该对象的成员函数、枚举类型、数据成员等信息,其实这就是反射机制;元对象系统提供的功能之一就是为 QObject 派生类对象提供运行时的类型信息及数据成员的当前值等信息。
        因为继承自QObject的类,才能启动元对象系统;所以,要通过反射机制来获取元对象信息,该类必须是QObject派生而来,且QObject应该位于基类继承列表的第一位。
        Qt 使用了一系列的类来实现反射机制,这些类对对象的各个方面进行了描述,其中QMetaObject 类描述了 QObject 及其派生类对象的所有元信息,该类是 Qt 元对象系
    统的核心类,通过该类的成员函数可以获取 QObject 及其派生类对象的所有元信息,包括:类名、父类、数据成员、函数成员、构造函数、枚举成员、属性等成员;在 Qt 中,这些成员分别使用了不同的类对其进行描述,比如函数成员使用类QMetaMethod 进行描述,属性使用 QMetaProperty 类进行描述,枚举使用QMetaMenu进行描述等。
        被Qt 中特殊的关键字及宏声明的函数或者变量才能被反射,比如Q_PROPERTY 宏、Q_INVOKABLE宏、slot、signals 等,因此普通函数想要被反射就需要用Q_INVOKABLE宏声明;变量想要被反射,需要用Q_PROPERTY 宏声明。
  2. 属性系统
        类属性是描述类的所有对象共同特征的一个数据项,,例如对象的高度、宽度、文本、颜色等;属性是一个类最重要的部分,了解了属性,也就了解了这个类能提供那些功能;Qt可以通过QObject::property()函数获取对象的属性,也可以通过QObject::setProperty()函数设置属性值;也可以在派生类(该类继承自QObject)时用QObject::Q_PROPERTY宏去声明一个自定义的属性,定义格式如下:
	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])

其中,方括号中的是可选项,各选项之间用空格隔开,变量及关键字释义:

  • type:指定属性的类型,可以是 QVariant 支持的类型或用户自定义类型,若是枚举类型,还需使用 Q_ENUMS 宏对枚举进行注册,若是自定义类型,需要使Q_DECLARE_METATYPE( Type )宏进行注册;
  • name:指定属性的名称。
  • READ getFunction:用于指定读取属性值的读取函数,其中 READ关键字表示读取,不可更改,getFunction用于指定函数名称;若没有指定 MEMBER 变量,则必须指定 READ 函数。通常, READ 函数是const 的, READ 函数必须返回属性的类型或对该类型的引用。
  • WRITE setFunction:用于指定设置属性值得函数,其中 关键字WRITE 表示写入,不可更改, setFunction用于指定函数名称。此函数必须只有一个参数,且返回值类型必须为 void。若为只读属性,则不需要指定 WRITE 函数。
  • MEMBER memberName:用于把指定的成员变量 memberName 设置为具有可读和可写性质, 而无需创建READ 和 WRITE 函数,其中 MEMBER 关键字表示成员,不可更改,memberName 表示类中的成员变量。若没有指定 READ 函数,则必须指定 MEMBER 变量。
  • RESET resetFunction: 用于把属性重置为默认值,该函数不能有参数,且返回值必
    须为 void。其中 RESET 表示重置,不可更改,resetFunction 用于指定函数名称。
  • NOTIFY notifySignal: 表示给属性关联一个信号。 如果设置该项,则应指定该类中
    已经存在的信号,每当属性值发生变化时就会发出该信号。若使用 MEMBER 变量,则NOTIFY 信号必须为零或一个参数, 且参数必须与属性的类型相同, 该参数将接受该属性的新值。
  • REVISION:设置版本号,默认为 0。
  • DESIGNABLE: 用于设置属性在 GUI 设计工具的属性编辑器(例如 Qt Designer)中是否可见,多数属性是可见的,默认值为 ture,即可见。 该变量也可以指定一个返回布尔值的成员函数替代 true 或 false。
  • SCRIPTABLE:设置属性是否可被脚本引擎访问,默认为 true。
  • STORED:指定了该属性是否是独立的或者是否依赖于别的属性。它也指定了当保存对象属性时是否会保存该属性。大多数的属性的STORED为真。但是,QWidget::minmunWidth()的STROED为false,因为它的值是从QQWidget::minimumSize()中取得的,它的类型是QSize。
  • USER:设置属性是否为可编辑的属性,每一个类只有一个 USER 属性(默认值为false)。 比如 QAbstractButton::checked 是用户可编辑的属性
  • CONSTANT:表明属性的值是常量,常量属性不能有WRITE函数和NOTIFY信号。对于同一个对象实例,每一次使用常量属性的 READ 函数都必须得到相同的值,但对于类的不同实例,这个值可以不同。
    FINAL:表示属性不能被派生类重写。

5.属性与动态属性
    属性是指该属性用Q_PROPERTY宏声明过,而且可以通过QMetaObject成员函数来获取属性信息;而动态属性是指该属性没有用Q_PROPERTY宏声明,动态属性是基于某个类的实例的,也就是说动态属性是添加到某个类的对象中的,而不是添加到QMetaObject 中的,这意味着,无法使用 QMetaObject 的成员函数获取动态属性的信息,但是依然可以通过property()函数获取动态属性的信息。
    当使用setProperty()函数设置属性值时,若该属性使用 Q_PROPERTY 进行了声明, 且值与属性 name 的类型兼容, 则把值 v存储在属性 name 中,并返回 true,若值与属性的类型不兼容则属性不会更改,并返回 false;若属性 name 未使用 Q_PROPERTY 进行声明,则把该属性和值作为新属性添加到对象中,并返回 false,这就是动态属性。动态属性仍可使用 property 进行查询。

二、程序

#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>

namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
    Q_OBJECT
public:
    explicit MainWindow(QWidget *parent = 0);
    ~MainWindow();
    //设置类信息
    Q_CLASSINFO("Author","CUI")
    Q_CLASSINFO("Version","V1.0.0")
    //定义年龄属性
    Q_PROPERTY(int age READ age WRITE setAge NOTIFY ageChanged RESET resetAge REVISION 1)
    int age();
    void setAge(int age);
    void resetAge();

    int m_age;
signals:
    void ageChanged(int age);
public slots:
    void propertyTestSlot(int age);

private:
    Ui::MainWindow *ui;
    //被Q_INVOKABLE定义的函数能被反射,可通过元对象系统获取
    Q_INVOKABLE void printPropertys(const QMetaObject *metObj);
};
#endif // MAINWINDOW_H

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QDebug>
#include <QMetaMethod>
#include <QMetaObject>
#include <QMetaClassInfo>

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    m_age = 18;
    connect(this,SIGNAL(ageChanged(int)),this,SLOT(propertyTestSlot(int)));
    
    // 1)设置已通过Q_PROPERTY声明的属性,测试该属性reset函数
    this->setProperty("age",QVariant(28));
    qDebug()<<this->property("age").toInt();
    this->resetAge();
    qDebug()<<this->property("age").toInt();


    // 2)设置动态属性并查询动态属性
    this->setProperty("sex",QVariant("男"));
    qDebug()<<this->property("sex").toString();


    // 3)打印自定义类信息
    const QMetaObject *metaObj = this->metaObject();
    for(int i = 0; i < metaObj->classInfoCount(); i++)
    {
         qDebug()<<metaObj->classInfo(i).name()<<metaObj->classInfo(i).value();
    }


    // 4)打印QMainWindow函数
    metaObj = this->metaObject();
    for(int i = 0; i < metaObj->methodCount(); i++)
    {
        qDebug()<<"QMainWindow函数"<<metaObj->method(i).typeName()<<metaObj->method(i).name();
    }


    // 5)打印MainWindow属性,及其父类的属性,通过QMetaObject::superClass获取父类的metaObject
    printPropertys(this->metaObject());
}

MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::printPropertys(const QMetaObject *metObj)
{
    QMetaProperty property;

    int propertyCount = 0;
    int propertyOffset = 0;

    const QMetaObject *met = nullptr;
    met = metObj;

    if(met != nullptr)
    {
        propertyCount = metObj->propertyCount();
        propertyOffset = metObj->propertyOffset();

        for(int i = propertyOffset; i < propertyCount; i++)
        {
            property = metObj->property(i);
            qDebug()<<metObj->className()<<"属性"<<QString(property.typeName())+"  "+QString(property.name());
        }
    }
    else
    {
        return;
    }

    met = nullptr;
    met = metObj->superClass();
    if(met != nullptr)
    {
        printPropertys(metObj->superClass());
    }
}

void MainWindow::propertyTestSlot(int age)
{
    qDebug()<<"age changed,new age is "<<age;
}

int MainWindow::age()
{
    return m_age;
}

void MainWindow::setAge(int age)
{
    m_age = age;
    emit ageChanged(age);
}

void MainWindow::resetAge()
{
    m_age = 18;
    emit ageChanged(m_age);
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值