QT学习:05 元对象系统

---
title: framework-cpp-qt-05-元对象系统
EntryName: framework-cpp-qt-05-mos
date: 2020-04-09 17:11:44
categories:
tags:
- qt
- c/c++
---

章节描述:
Qt 是一个用标准 C++ 编写的跨平台开发类库,它对标准 C++ 进行了扩展,引入了元对象系统、信号与槽、属性等特性,使应用程序的开发变得更高效。

Qt 的元对象系统

Qt 的元对象系统(Meta-Object System)提供了对象之间通信的信号与槽机制运行时类型信息动态属性系统

元对象系统由以下三个基础组成:

  1. QObject 类是所有使用元对象系统的类的基类。
  2. 在一个类的 private 部分声明 Q_OBJECT宏,使得类可以使用元对象的特性,如动态属性、信号与槽。
  3. MOC(元对象编译器)为每个 QObject 的子类提供必要的代码来实现元对象系统的特性。

元对象提供的一些功能

除了信号与槽机制外,元对象还提供如下一些功能:

  • QObject::metaObject() 函数返回类关联的元对象,元对象类 QMetaObject 包含了访问元对象的一些接口函数。例如 QMetaObject::className() 函数可在运行时返回类的名称字符串。
  QObject *obj = new QPushButton;
  obj->metaObject()->className (); //返回"QPushButton"
  • QMetaObject::newInstance() 函数创建类的一个新的实例。

  • QObject::inherits(const char *className) 函数判断一个对象实例是否是名称为 className 的类或 QObject 的子类的实例。例如:

  QTimer *timer = new QTimer; // QTimer 是 QObject 的子类
   timer->inherits ("QTimer"); // 返回 true
   timer->inherits ("QObject"); // 返回 true
   timer->inherits ("QAbstractButton");//返回 false,不是 QAbstractButton 的子类
  • QObject::tr()QObject::trUtf8() 函数可翻译字符串,用于多语言界面设计。

  • QObject::setProperty()QObject::property() 函数用于通过属性名称动态设置和获取属性值。

QObject 的动态转换

对于 QObject 及其子类,还可以使用 qobject_cast() 函数进行动态转换(dynamic cast)。

例如,假设 QMyWidgetQWidget 的子类并且在类定义中声明了 Q_OBJECT 宏。

创建实例使用下面的语句:

QObject *obj = new QMyWidget;

变量 obj 定义为 QObject 指针,但它实际指向 QMyWidget 类,所以可以正确转换为 QWidget,即:

QWidget *widget = qobject_cast<QWidget *>(obj);

从 QObject 到 QWidget 的转换是成功的,因为 obj 实际是 QMyWidget 类,是 QWidget 的子类。也可以将其成功转换为 QMyWidget,即:

QMyWidget *myWidget = qobject_cast<QMyWidget *>(obj);

转换为 QMyWidget 是成功的,因为 qobject_cast() 并不区分 Qt 内建的类型和用户自定义类型。但是,若要将 obj 转换为 QLabel 则是失败的,即:

QLabel * label = qobject_cast<QLabel *>(obj);

这样转换是失败的,返回指针 label 为 NULL,因为 QMyWidget 不是 QLabel 的子类。

使用动态转换,使得程序可以在运行时对不同的对象做不同的处理。

属性系统

属性定义

Qt 提供一个 Q_PROPERTY() 宏可以定义属性,它也是基于元对象系统实现的。Qt 的属性系统与 C++ 编译器无关,可以用任何标准的 C++ 编译器编译定义了属性的 Qt C++ 程序。

在 QObject 的子类中,用宏 Q_PROPERTY() 定义属性,其使用格式如下:

Q_PROPERTY(type name (READ getFunction [WRITE setFunction] | MEMBER meznberName [(READ getFunction | WRITE setFunction)])
    [RESET resetFunction]
    [NOTIFY notifySignal]
    [REVISION int]
    [DESIGNABLE bool]
    [SCRIPTABLE bool]
    [STORED bool]
    [USER bool]
    [CONSTANT]
    [FINAL])

Q_PROPERTY 宏定义一个返回值类型为 type,名称为 name 的属性,用 READ、WRITE 关键字定义属性的读取、写入函数,还有其他的一些关键字定义属性的一些操作特性。属性的类型可以是 QVariant 支持的任何类型,也可以用户自定义类型。

Q_PROPERTY 宏定义属性的一些主要关键字的意义如下:

  • 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)

属性的使用

不管是否用 READ 和 WRITE 定义了接口函数,只要知道属性名称,就可以通过 QObject::property() 读取属性值,并通过 QObject::setProperty() 设置属性值。例如:

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

动态属性

QObject::setProperty() 函数可以在运行时为类定义一个新的属性,称之为动态属性。动态属性是针对类的实例定义的。

动态属性可以使用 QObject::property() 查询,就如在类定义里用 Q_PROPERTY 宏定义的属性一样。

例如,在数据表编辑界面上,一些字段是必填字段,就可以在初始化界面时为这些字段的关联显示组件定义一个新的 required 属性,并设置值为“true”,如:

editName->setProperty("required", "true");
comboSex->setProperty("required", "true");
checkAgree->setProperty("required", "true");

然后,可以应用下面的样式定义将这种必填字段的背景颜色设置为亮绿色。

*[required="true"]{background-color:lime}

类的附加信息

属性系统还有一个宏 Q_CLASSINFO(),可以为类的元对象定义“名称——值”信息,如:

class QMyClass:public QObject {
    Q_OBJECT
    Q_CLASSINFO("author", "xx")
    Q_CLASSINFO ("company", "UPC")
    Q_CLASSINFO("version ", "1.0.1")
  public:
    ...
};

Q_CLASSINFO() 宏定义附加类信息后,可以通过元对象的一些函数获取类的附加信息,如 classlnfo(int) 获取某个附加信息,函数原型定义如下:

QMetaClassInfo QMetaObject::classInfo(int index) const

返回值是 QMetaClassInfo 类型,有 name() 和 value() 两个函数,可获得类附加信息的名称和值。

综合例程

Object.h

#ifndef OBJECT_H
#define OBJECT_H

#include <QObject>
#include <QString>
#include <QDebug>

class Object : public QObject
{
    Q_OBJECT
        Q_PROPERTY(int age READ age  WRITE setAge NOTIFY ageChanged)
        Q_PROPERTY(int score READ score  WRITE setScore NOTIFY scoreChanged)
        Q_CLASSINFO("Author", "Scorpio")
        Q_CLASSINFO("Version", "1.1")
        Q_ENUMS(Level)
protected:
    QString m_name;
    QString m_level;
    int m_age;
    int m_score;
public:
    enum Level
    {
        Basic,
        Middle,
        Advanced
    };

public:
    explicit Object(QString name)
    {
        m_name = name;
        setObjectName(m_name);
        connect(this, SIGNAL(ageChanged(int)), this, SLOT(onAgeChanged(int)));
        connect(this, SIGNAL(scoreChanged(int)), this, SLOT(onScoreChanged(int)));
    }

    int age() const
    {
        return m_age;
    }

    void setAge(const int& age)
    {
        m_age = age;
        emit ageChanged(m_age);
    }

    int score()const {  return m_score;  }

    void setScore(const int& score)
    {
        m_score = score;
        emit scoreChanged(m_score);
    }

signals:
    void ageChanged(int age);
    void scoreChanged(int score);

public slots:
    void onAgeChanged(int age)
    {
        qDebug() << "age changed:" << age;
    }
    void onScoreChanged(int score)
    {
        qDebug() << "score changed:" << score;
    }
};

#endif // OBJECT_H

Main.cpp

#include <QCoreApplication>
#include "myclass.h"
#include <QMetaObject>
#include <QMetaClassInfo>

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    Object ob("object");

    //设置属性age
    ob.setProperty("age", QVariant(30));
    qDebug() << "age: " << ob.age();
    qDebug() << "property age: " << ob.property("age").toInt();

    //设置属性score
    ob.setProperty("score", QVariant(90));
    qDebug() << "score: " << ob.score();
    qDebug() << "property score: " << ob.property("score").toInt();

    //内省intropection,运行时查询对象信息
    qDebug() << "object name: " << ob.objectName();
    qDebug() << "class name: " << ob.metaObject()->className();
    qDebug() << "isWidgetType: " << ob.isWidgetType();
    qDebug() << "inherit: " << ob.inherits("QObject");

    // 获取附加信息
    qDebug()<<ob.metaObject()->classInfo(0).name();
    qDebug()<<ob.metaObject()->classInfo(0).value();
    qDebug()<<ob.metaObject()->classInfo(1).name();
    qDebug()<<ob.metaObject()->classInfo(1).value();

    return a.exec();
}

Q_INVOKABLE

用于定义一个成员函数可以被元对象系统调用,Q_INVOKABLE宏必须写在函数的返回类型之前。如下:

Q_INVOKABLE void invokableMethod();

invokableMethod()函数使用了Q_INVOKABLE宏声明,invokableMethod()函数会被注册到元对象系统中,可以使用 QMetaObject::invokeMethod()调用。

Q_INVOKABLE与QMetaObject::invokeMethod均由元对象系统唤起,在Qt C++/QML混合编程、跨线程编程、Qt Service Framework以及 Qt/ HTML5混合编程以及里广泛使用。

在跨线程编程中的使用

如何调用驻足在其他线程里的QObject方法呢?Qt提供了一种非常友好而且干净的解决方案:向事件队列post一个事件,事件的处理将以调用所感兴趣的方法为主(需要线程有一个正在运行的事件循环)。而触发机制的实现是由MOC提供的内省方法实现的。因此,只有信号、槽以及被标记成Q_INVOKABLE的方法才能够被其它线程所触发调用。如果不想通过跨线程的信号、槽这一方法来实现调用驻足在其他线程里的QObject方法。另一选择就是将方法声明为Q_INVOKABLE,并且在另一线程中用invokeMethod唤起。

Qt Service Framework

Qt服务框架是Qt Mobility 1.0.2版本推出的,一个服务(service)是一个独立的组件提供给客户端(client)定义好的操作。

客户端可以通过服务的名称,版本号和服务的对象提供的接口来查×××。 查找到服务后,框架启动服务并返回一个指针。

服务通过插件(plug-ins)来实现。为了避免客户端依赖某个具体的库,服务必须继承自QObject,保证QMetaObject 系统可以用来提供动态发现和唤醒服务的能力。要使QmetaObject机制充分的工作,服务必须满足,其所有的方法都是通过 signal、slot、property或invokable method和Q_INVOKEBLE来实现。

QServiceManager manager;
QObject *storage ;  
storage = manager.loadInterface("com.nokia.qt.examples.FileStorage"); 
if(storage)     
    QMetaObject::invokeMethod(storage, "deleteFile", Q_ARG(QString, "/tmp/readme.txt")); 

上述代码通过service的元对象提供的invokeMethod方法,调用文件存储对象的deleteFile() 方法。客户端不需要知道对象的类型,因此也没有链接到具体的service库。 当然在服务端的deleteFile方法,一定要被标记为Q_INVOKEBLE,才能够被元对象系统识别。
Qt服务框架的一个亮点是它支持跨进程通信,服务可以接受远程进程。在服务管理器上注册后,进程通过signalslotinvokable methodproperty来通信,就像本地对象一样。服务可以设定为在客户端间共享,或针对一个客户端。

原文链接: https://www.cnblogs.com/schips/p/framework-cpp-qt-05-mos.html

欢迎关注

微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍;

也有高质量的技术群,里面有嵌入式、搜广推等BAT大佬

    QT学习:05 元对象系统

原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/340937

非原创文章文中已经注明原地址,如有侵权,联系删除

关注公众号【高性能架构探索】,第一时间获取最新文章

转载文章受原作者版权保护。转载请注明原作者出处!

(0)
上一篇 2023年3月2日 上午12:50
下一篇 2023年3月2日 上午12:51

相关推荐