【Qt进阶学习】:元对象系统学习(一)
元对象系统学习一一、元对象系统元对象是指用于描述另一个对象结构的对象。在Qt中当我们创建一个QObject子类并在类的私有区域调用了Q_OBJECT宏时会由QMetaobject类自动新建一个对象staticMetaObject这个对象就是用来描述这个子类的元对象。Qt的元对象系统元对象系统提供了内部对象之间通信的信号与槽机制运行时信息、属性系统、动态属性系统等。元对象系统基于以下三件事。QObject为所有要使用元对象系统的对象提供了一个基类必须在类声明的私有区域添加Q_OBJECT宏该宏用于使能元对象系统使能后才可以使用信号与槽功、动态属性等功能元对象编译器MOC)为每个QObject子类提供了实现元对象所必须的特性。MOCmoc是 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()为类构造一个新的实例。反射机制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 宏声明。属性系统类属性是描述类的所有对象共同特征的一个数据项例如对象的高度、宽度、文本、颜色等属性是一个类最重要的部分了解了属性也就了解了这个类能提供那些功能Qt可以通过QObject::property()函数获取对象的属性也可以通过QObjectsetProperty()函数设置属性值也可以在派生类该类继承自QObject时用QObject::Q_PROPERTY宏去声明一个自定义的属性定义格式如下Q_PROPERTY(typename(READ getFunction[WRITE setFunction]|MEMBER memberName[(READ getFunction|WRITE setFunction)])[RESET resetFunction][NOTIFY notifySignal][REVISIONint][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 进行查询。二、程序#ifndefMAINWINDOW_H#defineMAINWINDOW_H#includeQMainWindownamespaceUi{classMainWindow;}classMainWindow:publicQMainWindow{Q_OBJECTpublic:explicitMainWindow(QWidget*parent0);~MainWindow();//设置类信息Q_CLASSINFO(Author,CUI)Q_CLASSINFO(Version,V1.0.0)//定义年龄属性Q_PROPERTY(intage READ age WRITE setAge NOTIFY ageChanged RESET resetAge REVISION1)intage();voidsetAge(intage);voidresetAge();intm_age;signals:voidageChanged(intage);publicslots:voidpropertyTestSlot(intage);private:Ui::MainWindow*ui;//被Q_INVOKABLE定义的函数能被反射可通过元对象系统获取Q_INVOKABLEvoidprintPropertys(constQMetaObject*metObj);};#endif// MAINWINDOW_H#includemainwindow.h#includeui_mainwindow.h#includeQDebug#includeQMetaMethod#includeQMetaObject#includeQMetaClassInfoMainWindow::MainWindow(QWidget*parent):QMainWindow(parent),ui(newUi::MainWindow){ui-setupUi(this);m_age18;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)打印自定义类信息constQMetaObject*metaObjthis-metaObject();for(inti0;imetaObj-classInfoCount();i){qDebug()metaObj-classInfo(i).name()metaObj-classInfo(i).value();}// 4)打印QMainWindow函数metaObjthis-metaObject();for(inti0;imetaObj-methodCount();i){qDebug()QMainWindow函数metaObj-method(i).typeName()metaObj-method(i).name();}// 5)打印MainWindow属性,及其父类的属性通过QMetaObject::superClass获取父类的metaObjectprintPropertys(this-metaObject());}MainWindow::~MainWindow(){deleteui;}voidMainWindow::printPropertys(constQMetaObject*metObj){QMetaProperty property;intpropertyCount0;intpropertyOffset0;constQMetaObject*metnullptr;metmetObj;if(met!nullptr){propertyCountmetObj-propertyCount();propertyOffsetmetObj-propertyOffset();for(intipropertyOffset;ipropertyCount;i){propertymetObj-property(i);qDebug()metObj-className()属性QString(property.typeName()) QString(property.name());}}else{return;}metnullptr;metmetObj-superClass();if(met!nullptr){printPropertys(metObj-superClass());}}voidMainWindow::propertyTestSlot(intage){qDebug()age changed,new age is age;}intMainWindow::age(){returnm_age;}voidMainWindow::setAge(intage){m_ageage;emitageChanged(age);}voidMainWindow::resetAge(){m_age18;emitageChanged(m_age);}