简  述: 讲解元对象系统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_HExWidget.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_HExPerson.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】


