Qt类的元对象MetaObject

今天又重新看了一下这里的内容,总结一下:
也就是说,这个类继承到最后一定是一个QObject类才行,才能调用元对象系统。在调用元对象之前可以对自己设置的类进行类的属性的设置,是通过宏定义Q_PROPERTY的方式来进行的,比如:
QWidget类定义属性的一些例子如下:

Q_PROPERTY(bool  focus  READ  hasFocus)
Q_PROPERTY(bool  enabled  READ  isEnabled  WRITE  setEnabled)
Q_PROPERTY(QCursor  cursor  READ cursor WRITE setCursor RESET unsetCursor)

这样我们就定义的属性的一些信息。然后我们就可以调用到元系统的一些信息,首先我们要声明一个元对象,然后就可以使用父类的指针指向子类的对象,来调用自己想修改的属性信息。比如:

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

这样我们就可以修改到当前的元对象的一些信息了。
动态增加属性信息。如果我们需要对这个类增加一些新的属性的信息,我们是可以通过动态来增加的,不一定需要通过重新构造一个类来,可以通过QObject::setProperty()函数,是在运行的时候来增加的,因此也称为动态属性。
动态属性可以使用QObject::property()查询,就如在类定义里用Q_PROPERTY宏定义的属性一样。如:

editName->setProperty("required", "true");//增加了required属性
comboSex-> setProperty("required", "true");//增加了required属性
checkAgree-> setProperty("required", "true");//增加了required属性

还有一点是,我们可以对一个类增加附加信息,也就是相当于备注一样,要使用到宏Q_CLASSINFO()。
如:

class QMyClass : public QObject
  {
     Q_OBJECT
     Q_CLASSINFO("author", "Wang")//备注了作者的信息
     Q_CLASSINFO("company", "UPC")//备注了公司的信息
     Q_CLASSINFO("version ", "3.0.1")//备注了版本的信息
  public:
     ...
  };

怎么拿到自己的备注的信息呢?
可以通过:

QMetaClassInfo QMetaObject::classInfo(int index) const

返回值是QMetaClassInfo类型,有name()和value()两个函数,可获得类附加信息的名称和值。
这样我们就完全知道了元系统的作用,就是对所有的类,如果终其父类是QObject,那么我们就可以使用这个元系统,元系统中包括我们使用的信息和槽,这也就是为什么,我们的信息和槽的时候继承的类一定要来自QObject类。

核心特点

元对象系统

Qt的元对象系统(Meta-Object System)提供了对象之间通信的信号与槽机制、运行时类型信息和动态属性系统。
QObject类是所有使用元对象系统的类的基类。
1.QObject类是所有使用元对象系统的类的基类。
2.在一个类的private部分声明Q_OBJECT宏,使得类可以使用元对象的特性,如动态属性、信号与槽。
3.MOC(元对象编译器)为每个QObject的子类提供必要的代码来实现元对象系统的特性。

使用:
QObject::metaObject()函数返回类关联的元对象,元对象类QMetaObject包含了访问元对象的一些接口函数,例如QMetaObject::className()函数可在运行时返回类的名称字符串。

QObject *obj = new QPushButton;
obj->metaObject()->className();    // 返回 "QPushButton"
//元对象的函数的测试
void test01()
{
    QObject *obj=new QPushButton;
    qDebug()<<obj->metaObject()->className();//打印类信息的名字
    qDebug()<<obj->metaObject()->classInfoCount();
    qDebug()<<obj->metaObject()->classInfoOffset();
    QTimer *timer=new QTimer;

    QObject *obj1=obj->metaObject()->newInstance();//创建一个新得实例

    QString str="hello";
    qDebug()<<QObject::tr(str.toStdString().c_str());

    qDebug()<<timer->inherits("QTimer");//判断一个对象实例是否是类或者子类
    qDebug()<<timer->inherits("QObject");
    qDebug()<<timer->inherits("QAbstractButton");

    char* s="dffdf";
    qDebug()<<QObject::tr(str.toStdString().c_str());
    qDebug()<<QObject::trUtf8(s);

    //动态的投射
    QObject *obj2=new QWidget;
    QWidget *widget=qobject_cast<QWidget*>(obj2);

}

属性系统

//属性系统
void test02()
{
//1.定义属性
    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] )
//            READ指定一个读取属性值的函数,没有MEMBER关键字时必须设置READ。
//            WRITE指定一个设定属性值的函数,只读属性没有WRITE设置。
//            MEMBER指定一个成员变量与属性关联,成为可读可写的属性,无需再设置READ和WRITE。
//            RESET是可选的,用于指定一个设置属性缺省值的函数。
//            NOTIFY是可选的,用于设置一个信号,当属性值变化时发射此信号。
//            DESIGNABLE表示属性是否在Qt Designer里可见,缺省为true。
//            CONSTANT表示属性值是一个常数,对于一个对象实例,READ指定的函数返回值是常数,但是每个实例的返回值可以不一样。具有CONSTANT关键字的属性不能有WRITE和NOTIFY关键字。
//            FINAL表示所定义的属性不能被子类重载。

//如 QWidget的
//            Q_PROPERTY(bool  focus  READ  hasFocus)
//            Q_PROPERTY(bool  enabled  READ  isEnabled  WRITE  setEnabled)
//            Q_PROPERTY(QCursor  cursor  READ cursor WRITE setCursor RESET unsetCursor)

//2.属性的使用:
            QPushButton *button = new QPushButton;
            QObject *object = button;
            object->setProperty("flat", true);//设置属性值
            QVariant  isFlat= object->property("flat");//读取属性值
            qDebug()<<isFlat;
            
//3.动态属性:使用setPropertry()函数在类运行的时候定义一个新的属性
            //定义一个必填的字段,定义一个新的属性required属性,并设置为true
            //editName->setProperty("required",true);
            //comboSex->setProperty("required",true);
            //checkAgree-> setProperty("required", "true");
            //*[required="true"] {background-color: lime}  样式设置,将背景颜色设置为亮绿色
            
//4.类的附加信息:属性系统还有一个宏Q_CLASSINFO(),可以为类的元对象定义“名称——值”信息
          //Q_CLASSINFO("author","DOU")
          //Q_CLASSINFO("version","3.0.1")
}

信息与槽

//信息与槽
void test03()
{
//1.connect函数形式
//    1.1connect(发送信号者的指针,信号的指针,接收者的指针的信息,处理的槽函数的指针);
//    1.2使用了宏SIGNAL()和SLOT()指定信号和槽函数,而且如果信号和槽函数带有参数,还需注明参数类型
//    没有参数的时候connect(sender, SIGNAL(signal()), receiver, SLOT(slot()));
//    有参数的时候connect(spinNum, SIGNAL(valueChanged (int)), this, SLOT(updateStatus(int));
//    1.3connect(const QObject *sender, const QMetaMethod &signal, const QObject *receiver, const QMetaMethod &method, Qt::ConnectionType type = Qt::AutoConnection)
    

//    connect()函数,最后都有一个参数Qt::ConnectionType type,缺省值为Qt::AutoConnection。枚举类型Qt::ConnectionType表示了信号与槽之间的关联方式,有以下几种取值。    
//    Qt::AutoConnection(缺省值):如果信号的接收者与发射者在同一个线程,就使用Qt::Direct Connection方式;否则使用Qt::QueuedConnection方式,在信号发射时自动确定关联方式。
//    Qt::DirectConnection:信号被发射时槽函数立即执行,槽函数与信号在同一个线程。
//    Qt::QueuedConnection:在事件循环回到接收者线程后执行槽函数,槽函数与信号在不同的线程。
//    Qt::BlockingQueuedConnection:与Qt::QueuedConnection相似,只是信号线程会阻塞直到槽函数执行完毕。当信号与槽函数在同一个线程时绝对不能使用这种方式,否则会造成死锁。

//2.使用sender()获取信号发射者
    //在槽函数里,使用QObject::sender()可以获取信号发射者的指针。如果知道信号发射者的类型,可以将指针投射为确定的类型,然后使用这个确定类的接口函数。
    //QSpinBox *spinBox = qobject_cast<QSpinBox *>(sender());
    
//3.自定义信号
   //在类中信号中定义(不需要实现,但一定要声明)
   //signals:void    ageChanged( int  value);
}

实际使用Qt的元对象

创建一个元类(person)

person.h

class Person:public QObject//一定要继承于QObject
{
	//宏,使用信息与槽必须使用
	Q_OBJECT
	//设置类的附加信息
	Q_CLASSINFO("author","DouDou")
	Q_CLASSINFO("version","1.0.0")
	//设置类的属性值
	Q_PROPERTY(int age READ age WRITE setAge NOTUFY ageChanged)
	Q_PROPERTY(QString name MEMBER m_name)
	Q_PROPERTY(int score MEMBER m_score)
private://成员
	int m_age=20;//年龄
	QString m_name;//名字
	int m_score=60;//分数
public:
	explicit Person(QString fName,QObject* parent=nullptr);//构造函数
	int age();//获取年龄
	void setAge(int value);//设置年龄
	void incAge();//修改年龄
signals:
    void    ageChanged( int  value);//修改年龄的信号函数

public slots:
};

person.cpp

#include "qperson.h"

QPerson::QPerson(QString fName,QObject *parent) : QObject(parent)
{ //构造函数
    m_name=fName;
}

int QPerson::age()
{ //返回age
    return  m_age;
}

void QPerson::setAge(int value)
{//设置age
    m_age=value;
    emit ageChanged(m_age); //发射信号
}

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

//主界面的窗口.h

#ifndef QMYWIDGET_H
#define QMYWIDGET_H

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

namespace Ui {
class QmyWidget;
}

class QmyWidget : public QWidget
{
    Q_OBJECT

private:
    QPerson *boy;//创建了一个Person类指针 boy
    QPerson *girl;//创建了一个Person类指针 girl

public:
    explicit QmyWidget(QWidget *parent = 0);
    ~QmyWidget();

private:
    Ui::QmyWidget *ui;

signals:

private slots:
//自定义槽函数
    void   on_ageChanged(int  value);           //年龄改变处理函数
    void   on_spin_valueChanged(int arg1);      //spin输入得时候改变处理函数

//界面按钮的槽函数
    void on_btnClear_clicked();         //清除信息按键
    void on_btnBoyInc_clicked();        //boy年龄增加按键
    void on_btnGirlInc_clicked();       //girl年龄增加按键
    void on_btnClassInfo_clicked();     //类信息显示
};

#endif // QMYWIDGET_H

//函数的实现.cpp

#include "qmywidget.h"
#include "ui_qmywidget.h"
#include    <QMetaProperty>
#include <QDebug>

QmyWidget::QmyWidget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::QmyWidget)
{//构造函数
    ui->setupUi(this);

    //h文件中只是维护了指针,但是没有实际的对象,所以new一个对象
    boy=new QPerson("王小明");
    boy->setProperty("score",95);
    boy->setProperty("age",10);
    boy->setProperty("sex","Boy");//动态属性,新增加属性
//    connect(boy,SIGNAL(ageChanged(int)),this,SLOT(on_ageChanged(int)));
    connect(boy,&QPerson::ageChanged,this,&QmyWidget::on_ageChanged);
    //如果boy的年龄已经改变了,就调用函数on_ageChanged

    girl=new QPerson("张小丽");
    girl->setProperty("score",81);
    girl->setProperty("age",20);
    girl->setProperty("sex","Girl");//动态属性
    connect(girl,&QPerson::ageChanged,this,&QmyWidget::on_ageChanged);

    ui->spinBoy->setProperty("isBoy",true); //动态属性,给控件增加了属性
    ui->spinGirl->setProperty("isBoy",false);

//  不能使用此形式,因为QSpinBox有两种参数形式的valueChanged()信号
//    connect(ui->spinGirl,&QSpinBox::valueChanged,
//            this,&QmyWidget::on_spinBoy_valueChanged);
    connect(ui->spinGirl,SIGNAL(valueChanged(int)),
            this,SLOT(on_spin_valueChanged(int)));
    connect(ui->spinBoy,SIGNAL(valueChanged(int)),
            this,SLOT(on_spin_valueChanged(int)));
}

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

//功能,获取的是信号发送者的类的信息
void QmyWidget::on_ageChanged( int value)
{//响应QPerson的ageChanged()信号
    Q_UNUSED(value);//对没有使用value不告警
    QPerson *aPerson = qobject_cast<QPerson *>(sender()); //类型投射
    QString hisName=aPerson->property("name").toString(); //姓名
//    QString hisName=aPerson->name(); //获取姓名,错误,私有属性,没不办法获取到名字,只能通过接口或者属性去获取
    QString hisSex=aPerson->property("sex").toString(); //动态属性
    int hisAge=aPerson->age();//通过接口函数获取年龄
//    int hisAge=aPerson->property("age").toInt();//通过属性获得年龄

    ui->textEdit->appendPlainText(hisName+","+hisSex
                               +QString::asprintf(",年龄=%d",hisAge));
}

void QmyWidget::on_btnClear_clicked()
{//"清空文本框"按钮
    ui->textEdit->clear();
}

void QmyWidget::on_btnBoyInc_clicked()
{//"boy长大一岁"按钮
    boy->incAge();
}

void QmyWidget::on_btnGirlInc_clicked()
{//"girl长大一岁"按钮
    girl->incAge();
}

//spin的数值改变之后处理函数
//是spingoy和spingirl的处理函数是同一个,但是我们想他们的处理的函数的时候能够不一样
//所以在spinBox的时候根据属性值再去做处理
void QmyWidget::on_spin_valueChanged(int arg1)
{//响应界面上spinBox的valueChanged(int)信号
    Q_UNUSED(arg1);
    QSpinBox *spinBox = qobject_cast<QSpinBox *>(sender());
    //如果发送信号者的属性”isBoy“是真,则去处理不同的逻辑
    if (spinBox->property("isBoy").toBool())
        boy->setAge(spinBox->value());
    else
        girl->setAge(spinBox->value());
}

//获取元对象的信息
void QmyWidget::on_btnClassInfo_clicked()
{//"类的元对象信息"按钮
//    const QMetaObject *meta=boy->metaObject();
    const QMetaObject *meta=girl->metaObject();
//    const QMetaObject *meta=ui->spinBoy->metaObject();//对象转化为元对象
    QObject *obj=girl;
    ui->textEdit->clear();

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

    ui->textEdit->appendPlainText("property");
    qDebug()<<"meta->propertyOffset():"<<meta->propertyOffset();
    qDebug()<<"meta->propertyCount():"<<meta->propertyCount();
    //属性信息
    for (int i=meta->propertyOffset();i<meta->propertyCount();i++)
    {
        const char* propName=meta->property(i).name();
        ui->textEdit->appendPlainText(
           QString("属性名称=%1,属性值=%2").arg(propName).arg(obj->property(propName).toString()));
    }

    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()));
    }

}


效果截图:
在这里插入图片描述

说明:
程序参考于《Qt 5.9 C++开发指南》
程序中增加注释,方便理解和学习。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值