Qt之属性系统概览

简述

在认识Qt的属性系统之前,先理解一下什么是属性系统。通常情况,一种语言是无法认识另一种语言的语法组件的,比如python不认识C++的成员变量,C++也无法认识python中的成员变量,属性系统可以理解为不同语言之间沟通的桥梁,C++,QML,python,JS,CSS都能认识。这样,不同语言之间就可以交互了。

Qt的属性系统也是一种语言沟通桥梁,Qt通过它的属性系统把C++的语法元素暴露给QML,QSS等,这样它们就可以就行交互了。

Qt提供一个类似于其它编译器供应商提供的复杂属性系统(Property System)。Qt属性系统基于Qt元对象系统。作为一个编译器和平台无关的库,Qt不能够依赖于那些非标准的编译器特性,比如:__property或者[property]。Qt的解决方案适用于Qt支持平台下的任何标准C++编译器。

如何声明属性

要声明一个属性,需要在继承QObject的类中使用Q_PROPERTY()宏。基本语法如下:

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,WRITE,RESET等都是关键字。

下面是一些典型属性声明:

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

以最后一行为例进行说明,这行代码声明了一个 cursor 属性,指明了它是 QCursor 类型的,需要用cursor() 函数来读取这个属性值,用setCursor() 函数来修改属性值,用unsetCursor() 函数进行默认值设置。

对属性声明的一些语法说明:

  • 如果MEMBER关键字没有被指定,则一个READ访问函数是必须的。它被用来读取属性值。理想的情况下,一个const函数用于此目的,并且它必须返回的是属性类型或const引用。比如:QWidget::focus是一个只读属性,通过READ函数QWidget::hasFocus()访问。

  • 一个WRITE访问函数是可选的,用于设置属性的值。它必须返回void并且只能接受一个参数,属性的类型是类型指针或引用,例如:QWidget::enabled具有WRITE函数QWidget::setEnabled()。只读属性不需要WRITE函数,例如:QWidget::focus没有WRITE函数。

  • 如果READ访问函数没有被指定,则MEMBER变量关联是必须的。这使得给定的成员变量可读和可写,而不需要创建READ和WRITE访问函数。如果需要控制变量访问,仍然可以使用READ和WRITE函数而不仅仅是MEMBER(但别同时使用)。

  • 一个RESET函数是可选的,用于将属性设置为上下文指定的默认值。例如:QWidget::cursor有READ和WRITE函数QWidget::cursor()和QWidget::setCursor(),同时也有一个RESET函数QWidget::unsetCursor(),因为没有可用的QWidget::setCursor()调用可以确定的将cursor属性重置为上下文默认的值。RESET函数必须返回void类型,并且不带任何参数。

  • 一个NOTIFY信号是可选的。如果定义了NOTIFY,则需要在类中指定一个已存在的信号,该信号在属性值发生改变时发射。与MEMBER变量相关的NOTIFY信号必须有零个或一个参数,而且必须与属性的类型相同。参数保存的是属性的新值。NOTIFY信号应该仅当属性值真正的发生变化时发射,以避免被QML重新评估。例如:当需要一个没有显式setter的MEMBER属性时,Qt会自动发射信号。

  • 一个REVISION数字是可选的。如果包含了该关键字,它定义了属性并且通知信号被特定版本的API使用(通常是QML);如果没有包含,它默认为0。

  • DESIGNABLE属性指定了该属性在GUI设计器(例如:Qt Designer)里的编辑器中是否可见。大多数的属性是DESIGNABLE (默认为true)。除了true或false,你还可以指定boolean成员函数。

  • SCRIPTABLE属性表明这个属性是否可以被一个脚本引擎操作(默认是true)。除了true或false,你还可以指定boolean成员函数。

  • STORED属性表明了该属性是否是独立存在的还是依赖于其它属性。它也表明在保存对象状态时,是否必须保存此属性的值。大多数属性是STORED(默认为true)。但是例如:QWidget::minmunWidth()的STROED为false,因为它的值从QWidget::minimumSize()(类型为QSize)中的width部分取得。

  • USER属性指定了属性是否被设计为用户可见和可编辑的。通常情况下,每一个类只有一个USER属性(默认为false)。例如: QAbstractButton::checked是(checkable)buttons的用户可修改属性。注意:QItemDelegate获取和设置widget的USER属性。

  • CONSTANT属性的出现表明属性是一个常量值。对于给定的object实例,常量属性的READ函数在每次被调用时必须返回相同的值。对于不同的object实例该常量值可能会不同。一个常量属性不能具有WRITE函数或NOYIFY信号。

  • FINAL属性的出现表明属性不能被派生类所重写。有些情况下,这可以用于效率优化,但不能被moc强制执行。必须注意不能覆盖一个FINAL属性。

下面的示例,展示了如何使用MEMBER关键字将类成员变量导出为Qt属性。注意:NOTIFY信号必须被指定,这样才能被QML使用。

    Q_PROPERTY(QColor color MEMBER m_color NOTIFY colorChanged)
    Q_PROPERTY(qreal spacing MEMBER m_spacing NOTIFY spacingChanged)
    Q_PROPERTY(QString text MEMBER m_text NOTIFY textChanged)
    ...
signals:
    void colorChanged();
    void spacingChanged();
    void textChanged(const QString &newText);

private:
    QColor  m_color;
    qreal   m_spacing;
    QString m_text;

属性类型可以是QVariant支持的任何类型,或者是用户定义的类型。在这个例子中,类QDate被看作是一个用户定义的类型。

Q_PROPERTY(QDate date READ getDate WRITE setDate)

如何读写属性

一个属性可以使用常规函数QObject::property()和QObject::setProperty()进行读写,除了属性的名字,不用知道属性所在类的任何细节。下面的代码中,调用QAbstractButton::setDown()和QObject::setProperty()来设置属性“down”。

QPushButton *button = new QPushButton;
QObject *object = button;

button->setDown(true);
object->setProperty("down", true);

在这里,button有一个down的属性,这个属性的写函数是setDown,想要写down这个属性,可以通过直接调用setDown来实现,也可以通过setProperty加上相关参数来实现。在读写属性时无需对类的内部了解太多,可以在运行时期通过QObject、QMetaObject和QMetaProperties查询类属性。

QObject *object = ...
const QMetaObject *metaobject = object->metaObject();
int count = metaobject->propertyCount();
for (int i=0; i<count; ++i) {
    QMetaProperty metaproperty = metaobject->property(i);
    const char *name = metaproperty.name();
    QVariant value = object->property(name);
    ...
}

动态属性

QObject::setProperty()也可以用来在运行时期向一个类的实例添加新的属性。

当使用一个名字和值调用它时,如果QObject中一个指定名称的属性已经存在,并且如果给定的值与属性的类型兼容,那么,值就被存储到属性中,然后返回true。如果值与属性类型不兼容,属性的值就不会发生改变,会返回false。但是如果QObject中一个指定名称的属性不存在,一个带有指定名称和值的新属性就被自动添加到QObject中,但是依然会返回false。这意味着返回值不能用于确定一个属性是否被设置值,除非事先知道这个属性已经存在于QObject中。

注意:动态属性被添加到每一个实例中,即:它们被添加到QObject中,而不是QMetaObject。一个属性可以从一个实例中删除,通过传入属性名和非法的QVariant值给QObject::setProperty()。默认的QVariant构造器会构造一个非法的QVariant。

动态属性可用QObject::property()来查询。

自定义类型

被属性使用的自定义类型需要使用Q_DECLARE_METATYPE()宏注册,以便它们的值能被保存在QVariant对象中。这使得它们适用于在类定义时使用Q_PROPERTY()宏声明的静态属性,以及运行时创建的动态属性。

为类添加附加信息

与属性系统相对应的是一个附加宏 - Q_CLASSINFO()。用于添加name-value对到类的元对象中。例如:

Q_CLASSINFO("Version", "3.0.0")

和其它meta-data一样,类信息可以在运行时通过meta-object访问,详情见:QMetaObject::classInfo() 。

C++与QML交互

属性系统可以实现C++与QML的交互

C++代码如下

#ifndef STUDENT_H
#define STUDENT_H

#include <QObject>

class Student : public QObject
{
    Q_OBJECT
    Q_PROPERTY(QString name READ getName WRITE setName NOTIFY sigNameChanged)
public:
    explicit Student(QObject *parent = nullptr) : QObject(parent) {

    }
    ~Student(){}

    void setName(const QString & name) {
        if(name != m_name) {
            m_name = name;
            emit sigNameChanged(m_name);
        }
    }
    QString getName() const {return m_name;}
signals:
    void sigNameChanged(QString name);

private:
    QString m_name;
};

#endif // STUDENT_H

以及

#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include "student.h"

int main(int argc, char *argv[])
{
    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);

    QGuiApplication app(argc, argv);

    QQmlApplicationEngine engine;
    Student student;
    engine.rootContext()->setContextProperty("student", &student);
    engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
    if (engine.rootObjects().isEmpty())
        return -1;

    return app.exec();
}

QML代码如下

import QtQuick 2.9
import QtQuick.Window 2.2
import QtQuick.Controls 1.4
Window {
    visible: true
    width: 640
    height: 480

    Label {
        anchors.centerIn: parent
        text: student.name
    }

    Connections{
        target: student
        onSigNameChanged:{
            console.log("student name changed",name)
        }
    }
    Component.onCompleted: {
        student.name = "Shijia Yin"
    }
}

动态样式表

利用Qt的属性系统,可以实现动态样式表,也就是基于对象的属性来动态地改变样式表,案例代码如下所示:

#include "widget.h"
#include <QVariant>
#include <QDebug>
#include <QPushButton>
#include <QHBoxLayout>
Widget::Widget(QWidget *parent)
    : QWidget(parent)
{
    QPushButton *btn = new QPushButton;
    btn->setText("你好");
    btn->setProperty("back", true);
    btn->setStyleSheet("QPushButton[back = \"true\"] {background-color: yellow}");
    QHBoxLayout *hLayout = new QHBoxLayout;
    hLayout->addWidget(btn);
    setLayout(hLayout);
}

Widget::~Widget()
{

}

引用

Qt 之属性系统

QML 与 C++交互

Qt属性系统

Qt助手之style sheet

Qt助手之Property System

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值