简 述: 讲解元对象系统moc(Meat-Object System)的对象MetaObject和(含动态)属性Propert的用法。没想到这一篇会延期如此之久之久。 (此篇有大部分是借鉴书籍和或互联网),因为作者写的很棒,故大篇幅的直接借鉴过来了 。其中文中有些少部分是自己照着修改了一点,稍加改写而成的。

[TOC]


本文初发于 “偕臧的小站“,同步转载于此。


编程环境:

  💻: uos20 amd64 📎 Qt 5.11.3

  💻: MacOS 10.14.6 📎 Qt 5.12.6

  💻: win10 x64 📎 Qt 5.12.7


元对象系统:

​ Qt 的元对象系统 (Meta-Object System) 提供了对象之间通信的信号与槽机制、运行时类型信息和动态属性系统。

元对象系统由以下三个基础组成:

  • QObject类是所有使用元对象系统的类的基类。

  • 在一个类的private部分声明 Q OBJECT 宏,使得类可以使用元对象的特性,如动态属性、信号与槽。

  • MOC (元对象编译器)为每个 QObjeet 的子类提供必要的代码来实现元对象系统的特性。构建项目时,MOC工具读取 C++ 源文件,当它发现类的定义里有 Q_ OBJECT 宏时,它就会为这个类生成另外一个包含有元对象支持代码的 C++ 源文件,这个生成的源文件连同类的实现文件一起被编译和连接。

  • 除了信号与槽机制外,元对象还提供如下一些功能。

  • Qbjet::metaOject()函数返回类关联的元对象,元对象类 QMetaObject 包含了访问元对象的一些接口口函数,例如 QMetabjet:classNamec() 函数可在运行时返回类的名称字符串。

    QObject *obj = new QPushButton;
    obj->metaObject ()->classNane();  //返回"QPushButton
  • QMetaOjct::newInstance()函数创建类的一个新的实例。

  • Q0bjct:inherits(const char *className)函数判断一个对象实例是否是名称为 className 的类或 QObject 的子类的实例。例如:

    QTimer *timer = new QTimer;  // OTimer是oobject的子类
    timer->inherits ("QTimer");  //返回true
    timer->inherits ("QObject");  //返回true
    timer->inherits ("QAbstractButton");//返回false. 不是QAbatractButton的子类
  • QObject::tr()Qbjet::trUtf8() 函数可翻译字符串,用于多语言界面设计。

  • QObjct:setProperty()Q0bjct:property() 函数用于通过属性名称动态设置和获取属性值。

    对于 QObject 及其子类,还可以使用 qobject_cast() 函数进行动态投射(dynamic cast)。例如,假设 QMyWidget 是 QWidget 的子类并且在类定义中声明了Q_OBJECT宏。创建实例使用下面的语句:

    Q0bject *obj = new QMyWidget;

    变量 obj 定义为 QObject 指针,但它实际指向 QMyWidget 类,所以可以正确投射为 QWidget,即:

    Qwidget *widget = qobject_cast<Qwidget *>(obj);

    从 QObject 到 QWidget 的投射是成功的,因为 obj 实际是 QMyWidget 类,是 QWidge 的子类。也可以将其成功投射为 QMyWidget,即:

    QMyWidget *myWidget = qobject_cast<QMyWidget *>(obj);

    投射为 QMyWidget是成功的,因为 qoiect_cast() 并不区分 Qt 内建的类型和用户自定义类型。但是,若要将 obj 投射为 QLabel 则是失败的,因为 QMyWidget 不是 Qlabel 的子类。即:

    QLabe1 *labol = qobject_caot<QLabe1 *>(obj);  

属性系统:

属性定义:

Qt提供一个Q PROPERTY0宏可以定义属性,它也是基于元对象系统实现的。Qt 的属性系統与C++编译器无管,可以用任何柝准的C++编译器定义属性的Qt C++程序。

  • 在QObijct的子奬中,用宏Q PROPERTYO定文属性,其使用格式如下:

    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])

    Q_PROPERTY宏定义属性的一些主要关键字的意义如下。

    ● READ 指定一个读取属性值的函数,没有 MEMBER 关键字时必须设置READ.

    ● WRITE指定一个设定属性值的函数, 只读属性没有WRITE设置。

    ● MEMBER指定一个成员变量与属性关联,成为可读可写的属性,无需再设置READ和WRITE.

    ● RESET是可选的,用于指定一个设置属性缺省值的函数。

    ● NOTIFY是可选的,用于设置一个信号, 当属性值变化时发射此信号.

    ● CONSTANT表示属性値是一常数,対于一个対象实例,READ 指定的函数返回値是常数,但是每个实例的返回值可以不一-样。具有CONSTANT关键字的属性不能有WRITE和NOTIFY关键字。

    FINAL表示所定文的属性不能被子美重栽。QWidget类定义属性的一-些例子如下:

    Q_ PROPERTY (bool  focus READ hasFocus)
    Q_ PROPERTY(b0ol enabled READ isEnabled WRITE setEnabled)
    Q_ PROPERTY (QCursor cursor READ cursor WRITE setCursor RESET unsetCursor()

属性的使用:

不管是否用READ和WRITE定义了接口函数,只要知道属性名称就可以通过QObjct:property()读取属性值,并通过QObject:setProperty0设置属性值。例如:

QPushButton *button = new QPushButton;Q0bject *object = button;
object->setProperty("flat", true);
bool isFlat- object->property("flat")

动态属性:

QObject:setPropert()函数可以在运行时为类定义一个新的属性,称之为动态属性。动态属性是针对类的实例定义的。动态属性可以使用Qbjct:property()查询,就如在类定义里用 Q_PROPERTY 宏定义的属性一样。

例如,在数据表编辑界面上,一些字段是必填字段,就可以在初始化界面时为这些字段的关联显示组件定义一个新的required属性,并设置值为”true”,如:

editName->setProperty("required""true");
comboSex-> setProperty("required". "true"); 
checkAgree-> setProperty("required", "true");

然后,可以应用下面的样式定义将这种必填字段的背景颜色设置为亮绿色

*[required="true"] (background-color: lime)


类的附加信息:

属性系统还有一个宏Q CLASSINFO0.可以为类的元对象定义“名称-值” 信息,如:

class QMyC1ass : public QObject
{ 
  Q_OBJECT
  Q_CLASSINFO("author", "Wang" )
  Q_CLASSINFO("company", "UPC" )
  Q_CLASSINFO("version ""3.0.1")
  public:
  ...
}

用Q CLASSINFOQ宏定义附加类信息后,可以通过元对象的一些函数获取类的附加信息,如classInfo(int )获取某个附加信息,函数原型定义如下: .

QMetaClassInfo QMetaObject: :classInfo(int index) const

返回值是 QMetaClassInfo 类型,有name()和value()两个函数,可获得类附加信息的名称和值。


核心源码:

 写了一个例子:

ExPerson.h

#ifndef EXPERSON_H
#define EXPERSON_H

#include <QObject>

class ExPerson : public QObject
{
    Q_OBJECT

    //类的附加信息:名称————值
    Q_CLASSINFO("author", "touwoyimuli")
    Q_CLASSINFO("version", "1.0.0")
    Q_CLASSINFO("info", "Qt5 Meta Object and Property Example")

    //属性定义
    Q_PROPERTY(int age READ getAge WRITE setAge NOTIFY ageChanged)    //属性age; 方法getAge()和setAge()对其读写; 设置信号ageChanged()
    Q_PROPERTY(QString name MEMBER m_name)     //属性name 与类成员变量m_name关联
    Q_PROPERTY(int score MEMBER m_score)       //属性score与类成员变量m_score关联

public:
    explicit ExPerson(QString name, QObject *parent = nullptr);

public:
    int getAge();                //属性 READ 函数
    void setAge(int value);      //属性 WRITE 函数

    void incAge();               //单独写一个接口,与属性无关

signals:
    void ageChanged(int value);  //属性age发生改变的信号函数

private:
    int m_age = 5;
    QString m_name;
    int m_score = 50;

};

#endif // EXPERSON_H

ExWidget.h

#ifndef EXWIDGET_H
#define EXWIDGET_H

#include <QWidget>
#include "ExPerson.h"

namespace Ui {
class ExWidget;
}

class ExWidget : public QWidget
{
    Q_OBJECT

public:
    explicit ExWidget(QWidget *parent = nullptr);
    ~ExWidget();

private slots:
    void onAgeChange(int val);       //自定义的槽函数
    void onSpinValChange(int val);

    void onBtnClear();               //UI界面的槽函数
    void onBtnBoyInc();
    void onBtnGrilInc();
    void onClassInfo();

private:
    Ui::ExWidget *ui;

    ExPerson* m_boy;
    ExPerson* m_girl;
};

#endif // EXWIDGET_H

ExPerson.cpp

#include "ExPerson.h"

//加一个参后的构造函数
ExPerson::ExPerson(QString name, QObject *parent) : QObject(parent)
{
    m_name = name;
}

int ExPerson::getAge()
{
    return m_age;
}

void ExPerson::setAge(int value)
{
    m_age = value;
    emit ageChanged(m_age);  //发射信号
}

void ExPerson::incAge()
{
    m_age++;
    emit ageChanged(m_age);  //发射信号
}

ExWidget.cpp

#include "ExWidget.h"
#include "ui_ExWidget.h"
#include <QMetaProperty>
#include <QDebug>

ExWidget::ExWidget(QWidget *parent) : QWidget(parent), ui(new Ui::ExWidget)
{
    ui->setupUi(this);

    m_boy = new ExPerson("张三");
    m_boy->setProperty("score", 90);
    m_boy->setProperty("age", 20);
    m_boy->setProperty("sex", "Boy");           //动态属性
    connect(m_boy, &ExPerson::ageChanged, this, &ExWidget::onAgeChange);

    m_girl = new ExPerson("张丽");
    m_girl->setProperty("score", 80);
    m_girl->setProperty("age", 10);
    m_girl->setProperty("sex", "Gril");         //动态属性
    connect(m_girl, &ExPerson::ageChanged, this, &ExWidget::onAgeChange);

    ui->spinBoy->setProperty("isBoy", true);    //动态属性
    ui->spinGril->setProperty("isBoy", false);

    connect(ui->spinGril, SIGNAL(valueChanged(int)), this, SLOT(onSpinValChange(int)));
    connect(ui->spinBoy,  SIGNAL(valueChanged(int)), this, SLOT(onSpinValChange(int)));

    connect(ui->btnBoyAdd, SIGNAL(clicked()), this, SLOT(onBtnBoyInc()));
    connect(ui->btnGrilAdd, SIGNAL(clicked()), this, SLOT(onBtnGrilInc()));
    connect(ui->btnMetaObject, SIGNAL(clicked()), this, SLOT(onClassInfo()));
    connect(ui->btnClean, SIGNAL(clicked()), this, SLOT(onBtnClear()));

    setWindowTitle(QObject::tr("元对象MetaObject和(含动态)属性Propert的用法"));
}

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

void ExWidget::onAgeChange(int val)
{
    Q_UNUSED(val)   //参数val没使用,避免警告

    ExPerson* person = qobject_cast<ExPerson *>(sender());    //类型投射
    QString name = person->property("name").toString();
    QString sex = person->property("sex").toString();
    int age  = person->getAge();                             //通过接口函数,获得年龄
                                                             //或使用 int age  = person->property("age").toInt();
    ui->textEdit->appendPlainText(name+","+sex + QString::asprintf(",年龄=%d",age));
}

void ExWidget::onSpinValChange(int val)
{
    Q_UNUSED(val)

    QSpinBox* spin = qobject_cast<QSpinBox *>(sender());     //类型投射
    if (spin->property("isBoy").toBool())
        m_boy->setAge(ui->spinBoy->value());
    else
        m_girl->setAge(ui->spinGril->value());
}

void ExWidget::onBtnClear()
{
    ui->textEdit->clear();
}

void ExWidget::onBtnBoyInc()
{
    m_boy->incAge();
}

void ExWidget::onBtnGrilInc()
{
    m_girl->incAge();
}

void ExWidget::onClassInfo()
{
    const QMetaObject* meta = m_boy->metaObject();

    ui->textEdit->clear();
    ui->textEdit->appendPlainText("==元对象信息(Meta Object)===");
    ui->textEdit->appendPlainText(QString("类名称: %1\n").arg(meta->className()));
    ui->textEdit->appendPlainText("属性(property)");

    for (int i = meta->propertyOffset(); i < meta->propertyCount(); i++)
    {
        QMetaProperty prop = meta->property(i);
        const char* propName = prop.name();
        QString propValue = m_boy->property(propName).toString();
        ui->textEdit->appendPlainText(QString("属性名称=%1, 属性值= %2").arg(propName).arg(propValue));
    }

    ui->textEdit->appendPlainText("");
    ui->textEdit->appendPlainText("classInfo:");
    for (int i = meta->classInfoOffset(); i < meta->classInfoCount(); i++)
    {
        QMetaClassInfo classInfo = meta->classInfo(i);
        ui->textEdit->appendPlainText(QString("Name=%1, Value= %2").arg(classInfo.name()).arg(classInfo.value()));
    }

}

运行效果:

  附上的最后的运行效果图一览:


下载地址:

https://github.com/xmuli/QtExamples 【QtMeatObjectEx】