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