第二章Qt元对象系统


元对象系统是一个基于标准C++的扩展,为QT提供了新号与槽机制、实时类型信息、动态属性系统

1.什么是元对象?

在计算机科学中,元对象是这样一个东西:它可以操纵、创建、描述或执行其他对象。元对象描述的的对象称为基对象。元对象可能存在这样的信息:基础对象的类型、接口、类、方法、属性、变量、控制结构等。

2.元对象系统组成

  • QObject
    是QT对象模型的核心,绝大部分的QT类都是从这个类继承而来

  • Q_OBJECT
    Q_OBJECT宏必须出现在类定义的私有部分,用来开启信号和槽、动态属性系统,或QT元对象系统提供的其他服务

  • MOC
    MOC编译器为QObject子类提供了一些实现元对象特性所需要的一些代码。比如说信号,大家只是在类声明的时候声明了所需要的信息,在MOC编译时,会为信号添加函数定义。

3.信号与槽

所谓信号槽,实际就是观察者模式(发布-订阅模式)。当某个时间发生之后,比如按钮检测到自己被点击了一下,它就会发出一个信号(signal)。这种发出是没有目的的,类似广播。如果有对象对这个信号感兴趣,它就会使用连接(connect)函数,意思是,将想要处理的信号和自己的一个函数(称为槽-slot)绑定来处理这个信号。也就是说,信号发出时,被连接的槽函数会自动被回调。

信号和槽的本质

信号是由对象发射出去的消息,信号实际上是一个特殊的函数,不需要由程序员实现,而是由QT的moc实现。
槽实际上就是普通的函数。
当我们把对象的信号和槽绑定在一起之后,当信号触发时,与之绑定的槽函数将会自动调用,并把信号参数传递给槽函数。

绑定信号与槽

使用QObject::connect()函数实现

#	Mainwindow.ui
# 创建一个PushButton
# 	Mainwindow.h 在下面加入代码
public slots:
   void onButtonClicked();
# Mainwindow.cpp
#include "MainWindow.h"
#include "./ui_MainWindow.h"

MainWindow::MainWindow(QWidget *parent)
   : QMainWindow(parent)
   , ui(new Ui::MainWindow)
{
   ui->setupUi(this);
   auto con =connect(ui->pushButton,SIGNAL(clicked()),this,SLOT(onButtonClicked()));

   QObject::connect(ui->pushButton_2,&QPushButton::clicked,this,&MainWindow::onButtonClicked);

   //lambda函数写法
   connect(ui->pushButton_3,&QPushButton::clicked,[=](){
       disconnect(con);
       qInfo()<<"断开1的连接";
   });
}
MainWindow::~MainWindow()
{
   delete ui;
}

void MainWindow::onButtonClicked()
{
   qInfo()<<"我被点击了";
}

自定义槽

定义槽函数必须遵循一下规则

槽函数的类型

  • 成员函数
    • 普通成员函数
    • 静态成员函数
  • 全局函数
  • lambda表达式

自定义槽案例

#include <QApplication>
#include <QWidget>
#include<QPushButton>

class MyWindow:public QWidget
{
    Q_OBJECT
public:
    MyWindow():QWidget(NULL){}
public slots:
    void showWindow()
    {
        this->show();
    }
};

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    QPushButton* btn = new QPushButton("打开");
    btn->show();
    MyWindow *mw=new MyWindow();
    auto con = QObject::connect(btn,&QPushButton::clicked,mw,&MyWindow::showWindow);
    return a.exec();
}

#include "main.moc"

自定义信号

自定义信号需要遵循以下规则

  • 信号是类的成员函数,并且返回类型必须是void
  • 信号函数只需要声明,不需要定义
  • 参数可以随意指定,信号也支持重载
  • 信号需要使用signals关键字进行声明
  • 在程序中发送自定义信号:发送信号的本质就是调用信号函数emit mysignals()
  • emit是个空宏,没有特殊含义,仅用来表示这个语句是发射一个信号,不写可以,但不推荐
#include <QApplication>
#include <QWidget>
#include<QPushButton>

class MyWindow:public QWidget
{
    Q_OBJECT
public:
    MyWindow():QWidget(NULL){}
signals:
    void isShow(QString text);
public slots:
    void showWindow()
    {
        this->show();
        emit isShow("我出来了");
    }
};

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    QPushButton* btn = new QPushButton("打开");
    btn->show();
    MyWindow *mw=new MyWindow();
    auto con = QObject::connect(btn,&QPushButton::clicked,mw,&MyWindow::showWindow);

    QObject::connect(mw,&MyWindow::isShow,btn,&QPushButton::setText);
    return a.exec();
}

#include "main.moc"

信号和槽重载二义性问题

解决方法


#include <QApplication>
#include <QWidget>
#include<QPushButton>

class MyWindow:public QWidget
{
    Q_OBJECT
public:
    MyWindow():QWidget(NULL){}
signals:
    void isShow();
    void isShow(QString text);

public slots:
    void showWindow()
    {
        this->show();
        emit isShow("我出来了");
    }
    void showWindow(bool t)
    {
        this->close();
        emit isShow();
    }
};

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    QPushButton* btn = new QPushButton("打开");
    btn->move(10,10);
    btn->show();
    QPushButton* btn1 = new QPushButton("关闭");
    btn1->move(10,100);
    MyWindow *mw=new MyWindow();
    //用overload重载
    QObject::connect(btn,&QPushButton::clicked,mw,QOverload<>::of(&MyWindow::showWindow));
    QObject::connect(mw,QOverload<QString>::of(&MyWindow::isShow),btn,&QPushButton::setText);
    //用qt4方式SIGNAL和SLOT宏
    QObject::connect(btn1,SIGNAL(clicked(bool)),mw,SLOT(showWindow(bool)));//SIGNAL和SLOT要前后一致
    QObject::connect(mw,SIGNAL(isShow()),btn1,SLOT(close()));
    QObject::connect(mw,SIGNAL(isShow(QString)),btn1,SLOT(show()));
    return a.exec();
}

#include "main.moc"

4.内存管理

1. 简介

C++中new 和delete必须配对使用,防止内存泄露。Qt中使用了new却很少delete,因为Qt实现了其独特的内存管理机制。
QObject以对象树的形式组织起来。当为一个对象创建子对象时,子对象会自动地添加到父对象的children()列表中。父对象拥有子对象的所有权,比如父对象可以在自己的析构函数中删除它的子对象。使用findChild()或findChildren()通过名字和类型查询孩子对象。

QObject(QObject *parent=nullptr)
  1. QObject及其派生类的对象,如果其parent非nullptr,那么其parent析构时会析构该对象。
  2. 父子关系:父对象、子对象、父子关系。这是Qt中所特有的,与类的继承关系无关,传递参数是与parent有关

2.关联图

在Qt中,最基础和核心的类是QObject,QObject内部有一个名为children的QObjectList列表,会保存所有子对象,还有一个指针parent,用来指向父对象,当自己析构时,会先把自己从parent列表中删除,并且析构所有的child
Qt元对象系统-LMLPHP

3.详解

  1. 指定父对象释放,自己也释放

#include <QApplication>
#include <QWidget>
#include<QPushButton>

class MyWindow:public QWidget
{
    Q_OBJECT
public:
    MyWindow(QWidget* parent=nullptr):QWidget(parent){}
    ~MyWindow()
    {
        qInfo()<<__FUNCTION__<<"释放了";
    }
signals:
    void isShow();
    void isShow(QString text);

public slots:
    void showWindow()
    {
        this->show();
        emit isShow("我出来了");
    }
    void showWindow(bool t)
    {
        this->close();
        emit isShow();
    }
};

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    QPushButton* btn = new QPushButton("打开");
    btn->move(10,10);
    btn->show();
    QPushButton* btn1 = new QPushButton("关闭");
    btn1->move(10,100);
    MyWindow *mw=new MyWindow(btn1);
    //用overload重载
    QObject::connect(btn,&QPushButton::clicked,mw,QOverload<>::of(&MyWindow::showWindow));
    QObject::connect(mw,QOverload<QString>::of(&MyWindow::isShow),btn,&QPushButton::setText);
    //用qt4方式SIGNAL和SLOT宏
    QObject::connect(btn1,SIGNAL(clicked(bool)),mw,SLOT(showWindow(bool)));//SIGNAL和SLOT要前后一致
    QObject::connect(mw,SIGNAL(isShow()),btn1,SLOT(close()));
    QObject::connect(mw,SIGNAL(isShow(QString)),btn1,SLOT(show()));
    btn1->deleteLater();
    return a.exec();
}

#include "main.moc"

  1. 查找子对象
	mw->setObjectName("window");
    auto wd =btn1->findChild<MyWindow*>("window"); //找子对象
    if(wd)
        qInfo()<<"存在";

4.智能指针

  • 使用方法
	#include<QPointer>
	
    QPointer<QPushButton> btn1 = new QPushButton("关闭");
    btn1->move(10,100);
    MyWindow *mw=new MyWindow(btn1.data());
	#include<QSharedPointer>
	
	QSharedPointer<QPushButton> btn1(new QPushButton("关闭"));
    QObject::connect(btn1.data(),SIGNAL(clicked(bool)),mw,SLOT(showWindow(bool)));//SIGNAL和SLOT要前后一致
    QObject::connect(mw,SIGNAL(isShow()),btn1.data(),SLOT(close()));
    QObject::connect(mw,SIGNAL(isShow(QString)),btn1.data(),SLOT(show()));
	#include<QScopedArrayPointer>
	
    QScopedArrayPointer<int> arrptr(new int[10]);
    arrptr[1] = 666;

5.属性系统

属性的行为类似于数据成员,但它具有通过元对象系统访问的附加特性
Qt元对象系统-LMLPHP

获取/设置属性值

获取

    MyWindow *mw=new MyWindow();
    mw->show();
	 qInfo()<< mw->property("pos");//结果QVariant(QPoint, QPoint(639,249))

设置

    QObject::connect(btn,&QPushButton::clicked,[&](){
        auto pos = mw->property("pos").toPoint();
        mw->setProperty("pos",QPoint(pos.x()-2,pos.y()-2));
    });

自己声明属性值

要声明属性,请在继承QObject的类中使用Q_PROPERTY()宏

Q_PROPRETY(type name
		(READ getFunction [WRITE setFunction]|
		MEMBER memberName [(READ getFunction|WRITE setFunction)])
		[RESET restFunction]
		[NOTIFY notifySignal]
		[REVISION int | REVISION(int[,int])]
		[DESIGNABLE bool]
		[SCRIPTABLE bool]
		[STORED bool]
		[USER bool]
		[BINDABLE bindableProperty]
		[CONSTANT]
		[FINAL]
		[REQUIRED])

属性的行为类似于数据成员,但它具有通过元对象系统访问的附加特性。

  • 如果未指定MEMBER变量,则需要READ访问器函数。它用于读取属性值。理想情况下,const函数用于此目的,它必须返回属性的类型或对该类型的const引用。例如,QWidget::focus是一个带有READ函数的只读属性,QWidget::hasFocus()。
  • WRITE访问器函数是可选的。它用于设置属性值。它必须返回void并且必须只接受一个参数,该参数可以是属性类型,也可以是指向该类型的指针或引用。例如,QWidget::enabled具有WRITE函数QWidget::setEnabled()。只读属性不需要WRITE函数。例如,QWidget::focus没有Write功能。
  • 如果未指定READ访问器函数,则需要MEMBER变量关联。这使得给定的成员变量可读可写,而无需创建READ和WRITE访问器函数。如果您需要控制变量访问,除了MEMBER变量关联之外,仍然可以使用READ或WRITE访问器函数。
  • RESET功能是可选的。它用于将属性设置回特定于上下文的默认值。
  • NOTIFY信号是可选的。如果已定义,则应指定该类中的一个现有信号,该信号在属性值更改时发出。MEMBER变量的NOTIFY信号必须采用零个或一个参数,该参数必须与属性的类型相同。该参数将采用属性的新值。NOTIFY信号只应在属性真正被更改时发出,以避免在QML中不必要地重新评估绑定。
  • REVISION编号或REVISION()宏是可选的。如果包含,它定义了要在API的特定修订版中使用的属性及其通知信号(通常用于暴露于QML)。如果不包含,则默认为0.
  • DESIGNABLE属性指示该属性是否应在GUI设计工具(如Qt Designer)的属性编辑器中可见。大多数属性是可设计的(默认为true)。有效值为真和假。
  • SCRIPTABLE属性指示脚本引擎是否可以访问此属性(默认为true)。有效值为真和假。
  • STORED属性指示该属性是否应该被认为是独立存在的,或取决于其他值。它还指示在存储对象的状态时是否必须保存属性值。大多数属性值是STORED的(默认为true)。
  • USER属性指示该属性是被指定为该类的面向用户的属性还是用户可编辑的属性。通常,每个类只有一个USER属性(默认false)。
  • BINDABLE bindableProperty属性表明该属性支持绑定,并且可以通过对元对象系统设置和检查该属性的绑定。bindableProperty命名QBindable类型的类成员,其中T是属性类型。
  • CONSTANT属性的存在表明属性值是常量。对于给定的对象实例,常量属性的READ方法在每次调用时都必须返回相同的值。对于对象的不同实例,该常数值可能不同。常量属性不能有WRITE方法或NOTIFY信号。
  • FINAL属性的存在表明该属性不会被派生类覆盖。这在某些情况下可用于性能优化,但并非由moc强制执行。必须注意永远不要覆盖FINAL属性。
  • REQUIRED属性的存在表明该属性应该由该类的用户设置。这不是由moc强制执行的,并且对于暴露给QML的类最有用。在QML中除非设置了所有的REQUIRED属性,否则无法实例化具有REQUIRED属性的类。

#include <QApplication>
#include <QWidget>
#include<QPushButton>
#include<QPointer>

class MyWindow:public QWidget
{
    Q_OBJECT
    Q_PROPERTY(QString name READ getname WRITE setname NOTIFY nameChanged FINAL)
    QString m_name;
public:
    MyWindow(QWidget* parent=nullptr):QWidget(parent){}
    ~MyWindow()
    {
        qInfo()<<__FUNCTION__<<"释放了";
    }
signals:
    void isShow();
    void isShow( QString text);
signals:
    void nameChanged();
public slots:
    QString getname()
    {
        qInfo()<<__FUNCTION__;
        return m_name;
    }
    void setname(QString name)
    {
        qInfo()<<__FUNCTION__;
        m_name = name;
        emit nameChanged();
    }
public slots:
    void showWindow()
    {
        this->show();
        emit isShow("我出来了");
    }
    void showWindow(bool t)
    {
        this->close();
        emit isShow();
    }
};

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    QPushButton* btn = new QPushButton("打开");
    btn->move(10,10);
    btn->show();
    MyWindow *mw=new MyWindow();
    mw->showWindow();

    QObject::connect(btn,&QPushButton::clicked,[=](){
        auto pos = mw->property("pos").toPoint();
        mw->setProperty("pos",QPoint(pos.x()-2,pos.y()-2));
        mw->setProperty("name","abc");//自定义的name属性改变
    });

    QObject::connect(mw,&MyWindow::nameChanged,[&,btn](){
        auto windowName = mw->property("name");
        btn->setText(windowName.toString());
    });


    return a.exec();
}

#include "main.moc"

直接关联也可以

 Q_PROPERTY(QString name  MEMBER m_name NOTIFY nameChanged FINAL)

绑定属性


#include <QApplication>
#include<QProperty>


struct Rectangle
{
    Rectangle() {}
    int w,h,area;
    Rectangle(int w,int h):w(w),h(h)
    {
        area = this->w*this->h;
    }
};
struct Rect
{
    Rect() {}
    QProperty<int> w,h,area;
    Rect(int w,int h):w(w),h(h)
    {
        //设置绑定属性
        area.setBinding([&]{return this->w*this->h;});
    }
};

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    //没设置绑定属性
    Rectangle rl(10,10);
    qInfo()<<rl.area;
    rl.h =20;
    qInfo()<<rl.area;
    //2个结果一样,都是100,没改变

    //设置绑定属性后
    Rect r(10,10);
    qInfo()<<r.area;
    r.h = 20;
    qInfo()<<r.area;
    //结果分别是100,和200
    return a.exec();
}

QObjectBindableProperty


#include <QApplication>
#include<QProperty>
#include<QWidget>
#include <QPushButton>


class MyWindow:public QWidget
{
    Q_OBJECT
public:
    MyWindow(QWidget* parent=nullptr):QWidget(parent){}

signals:
    void myxChanged();

public:
    Q_OBJECT_BINDABLE_PROPERTY(MyWindow,int,myx,&MyWindow::myxChanged);

};

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    QPushButton* btn = new QPushButton("打开");
    btn->move(10,10);
    btn->show();
    MyWindow* mw = new MyWindow();
    QObject::connect(btn,&QPushButton::clicked,[=]{
        static int a=0;
        mw->show();
        mw->myx = ++a;
    });
    QObject::connect(mw,&MyWindow::myxChanged,[&]{
        qInfo()<<"x修改了";
    });

    return a.exec();
}

#include "main.moc"

6.实时类型信息

判断继承自XX类

qInfo()<<mw->inherits("QObject");//true

枚举

#include <QMetaEnum>

namespace Yerennuo {
//命名空间
Q_NAMESPACE
enum Type
{
    Player,
    Enemy,
    Bullet
};
//注册枚举
Q_ENUM_NS(Type)
}


qInfo()<< Yerennuo::Type::Bullet;//Yerennuo::Bullet

也可以在类里面

class Test:public QObject
{
    Q_OBJECT
public:
    enum Type
    {
        Player,
        Enemy,
        Bullet
    };
    Q_ENUM(Type)
};

    qInfo()<<Test::Type::Player;//Test::Player

QMetaObject元信息


#include "qmetaobject.h"
#include <QApplication>
#include<QWidget>
#include <QMetaObject>


class MyWindow:public QWidget
{
    Q_OBJECT
    Q_CLASSINFO("author","yerennuo")
public:
    MyWindow(QWidget* parent=nullptr):QWidget(parent){}

};



int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    MyWindow* mw = new MyWindow();

    auto metaobj = mw->metaObject();
     qInfo()<<"key="<<metaobj->classInfo(0).name()
             <<"value="<<metaobj->classInfo(0).value();
	//key= author value= yerennuo
    return a.exec();
}

#include "main.moc"

04-02 12:35