深入理解Qt属性系统[Q_PROPERTY]

Qt 属性系统是 Qt 框架中一个非常核心和强大的部分,它提供了一种标准化的方法来访问对象的属性。这一系统不仅使得开发者能够以一致的方式处理各种数据类型,还为动态属性的管理提供了支持,并与 Qt 的元对象系统紧密集成。在这篇文章中,我们将详细介绍 Qt 属性系统的概念、实现机制和使用方式,以及它如何帮助开发者提升应用程序的灵活性和可维护性。

Q_PROPERTY

声明属性的类必须要继承QObject,并且使用Q_OBJECT宏,然后通过Q_PROPERTY宏来声明属性。

源码解释

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

基本结构

  • type:属性的数据类型,支持以下类型:
    1. Qt内建类型:常见的Qt数据类型如QStringQDateQTimeQDateTimeQUrlQListQMapQStringListQCharQByteArray等。
    2. 标准C++类型intbooldoublefloatchar*等。
    3. 枚举类型
    4. 自定义类型:自定义的类也可以作为属性的类型,但是要满足以下条件:
      1. 类必须被Qt的元对象系统所知晓,这通常意味着类需要使用Q_DECLARE_METATYPE宏声明。
      2. 类需要提供公共的构造函数、拷贝构造函数和赋值运算符,以便Qt能够在内部处理属性的复制和赋值。
      3. 如果要通过QVariant进行存储或传递,类必须注册到Qt的类型系统中,使用qRegisterMetaType<ClassName>()
  • name:属性的名称

注意:这里的name和具体的成员变量名可以不同,因为Q_PROPERTY声明的属性不是具体的成员,属性是用来关联对应某个成员对象的。

访问函数

  • READ:READ访问器函数,即读取属性值的函数。函数的返回值必须是属性对应类型或对该类型的const引用,函数体必须带const修饰。函数示例如下:

type getNameFunction() const;
const type& getNameFunction() const;

  • WRITE:WRITE访问器函数,即修改属性值的函数。函数的返回值必须是void类型,并且只接受一个参数,可以是属性对应的类型,也可以是指向该类型的指针或引用。函数示例如下:

void setNameFunction(type val);
void setNameFunction(const type& val);

  • MEMBER:直接指定类中的成员变量作为属性的存储,代替使用单独的读写方法。可以与READWRITE搭配使用来覆盖默认的成员访问。

可选修饰符

  • RESET:指定一个函数用于重置属性到默认的初始状态。通常不带参数,没有返回值。
  • NOTIFY:指定当属性值改变时要发出的信号。这在绑定和数据驱动的界面更新中非常有用。
  • REVISION:用于版本控制,指明属性从哪个Qt版本开始可用。这主要用于与QML集成时的版本控制。
  • DESIGNABLE:指定属性是否应该在Qt设计器中显示,默认为true
  • SCRIPTABLE:指定属性是否可以通过脚本访问,默认为true
  • STORED:指明属性是否应该被序列号,默认为true。如果属性是计算出来的,并不需要存储,可以设置为false
  • USER:指明这个属性是否被指定为该类的面向用户的属性或用户可编辑的属性。通常,每个类只有一个USER属性,默认为false
  • CONSTANT:标记属性为常量,一旦在构造函数中设置后不能改变。常用于只读的配置值。
  • FINAL:指明这个属性在派生类中不能被重写。
  • REQUIRED:属性的存在表明该属性应由该类的用户设置。这不是由 moc 强制执行的,并且对于暴露于 QML 的类来说最有用。在 QML 中,除非设置了所有必需属性,否则无法实例化具有必需属性的类。

使用示例

注意

  1. 使用READWRITE两个访问器函数在未指定MEMBER时需要显式定义
  2. 在指定MEMBER时可以不用显式定义READWRITE两个访问器函数
  3. READWRITE两个访问器函数主要的作用不是显式调用相应的函数,而是通过对象的property()setProperty()来调用

只读属性

class Data : public QObject {Q_OBJECTQ_PROPERTY(QString name READ name FINAL)
public:explicit Data(QObject* parent = nullptr): QObject(parent){}QString name() const{return m_name;}private:QString m_name { "Data" };
};int main(int argc, char* argv[])
{QCoreApplication a(argc, argv);Data data;// 使用 property() 来获取属性值qDebug() << "Initial name:" << data.property("name").toString();// 使用 setProperty() 来修改属性值data.setProperty("name", "Updated Name");qDebug() << "Updated name:" << data.property("name").toString();return a.exec();
}

输出结果:

Initial name: "Data"
Updated name: "Data"

可读写属性

class Data : public QObject {Q_OBJECTQ_PROPERTY(QString name READ name WRITE setName FINAL)
public:explicit Data(QObject* parent = nullptr): QObject(parent){}QString name() const{return m_name;}void setName(const QString& name){if (name != m_name)m_name = name;}private:QString m_name { "Data" };
};int main(int argc, char* argv[])
{QCoreApplication a(argc, argv);Data data;// 使用 property() 来获取属性值qDebug() << "Initial name:" << data.property("name").toString();// 使用 setProperty() 来修改属性值data.setProperty("name", "Updated Name");qDebug() << "Updated name:" << data.property("name").toString();return a.exec();
}

输出结果:

Initial name: "Data"
Updated name: "Updated Name"

MEMBER关键字

class Data : public QObject {Q_OBJECTQ_PROPERTY(QString name MEMBER m_name FINAL)
public:explicit Data(QObject* parent = nullptr): QObject(parent){}private:QString m_name { "Data" };
};int main(int argc, char* argv[])
{QCoreApplication a(argc, argv);Data data;// 使用 property() 来获取属性值qDebug() << "Initial name:" << data.property("name").toString();// 使用 setProperty() 来修改属性值data.setProperty("name", "Updated Name");qDebug() << "Updated name:" << data.property("name").toString();return a.exec();
}

输出结果:

Initial name: "Data"
Updated name: "Updated Name"

使用MEMBER关键字时,用户无法通过实例对象显式调用gettersetter函数,只能通过property()setProperty()来获取和设置。

NOTIFY关键字的使用

class Data : public QObject {Q_OBJECTQ_PROPERTY(QString name MEMBER m_name NOTIFY nameChanged FINAL)
public:explicit Data(QObject* parent = nullptr): QObject(parent){}signals:void nameChanged(const QString& name);private:QString m_name { "Data" };
};int main(int argc, char* argv[])
{QCoreApplication a(argc, argv);Data data;QObject::connect(&data, &Data::nameChanged, [&data](const QString& name) {qDebug() << "nameChanged: " << data.property("name");});// 使用 property() 来获取属性值qDebug() << "Initial name:" << data.property("name").toString();// 使用 setProperty() 来修改属性值data.setProperty("name", "Updated Name");qDebug() << "Updated name:" << data.property("name").toString();return a.exec();
}

输出结果:

Initial name: "Data"
nameChanged:  QVariant(QString, "Updated Name")
Updated name: "Updated Name"

属性系统的主要应用场景

C++和QML交互

首先,我们需要定义一个包含可观察属性的C++类。此类将发出属性变化的信号,QML界面将响应这些信号更新显示。
CppBackend.h

#ifndef CPPBACKEND_H
#define CPPBACKEND_H#include <QObject>
#include <QString>class CppBackend : public QObject {Q_OBJECTQ_PROPERTY(QString message READ message WRITE setMessage NOTIFY messageChanged)public:explicit CppBackend(QObject *parent = nullptr) : QObject(parent), m_message("Hello from C++!") {}QString message() const { return m_message; }public slots:void setMessage(const QString &message) {if (m_message != message) {m_message = message;emit messageChanged();}}signals:void messageChanged();private:QString m_message;
};#endif // CPPBACKEND_H

接下来,我们创建一个QML文件来显示message属性,并提供一个按钮来改变它。
main.qml

import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.15Window {width: 640height: 480visible: truetitle: qsTr("Hello World")Column {anchors.centerIn: parentspacing: 20Text {id: displayTexttext: backend.messagefont.pointSize: 20}Button {text: "Change Message"onClicked: {backend.message = "Updated by QML!"}}}
}

在主程序中,我们将注册后端对象为QML可访问,并加载QML文件。
main.cpp

#include "CppBackend.h"
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>int main(int argc, char* argv[])
{
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
#endifQGuiApplication app(argc, argv);QQmlApplicationEngine engine;CppBackend backend;engine.rootContext()->setContextProperty("backend", &backend);const QUrl url(QStringLiteral("qrc:/main.qml"));QObject::connect(&engine,&QQmlApplicationEngine::objectCreated,&app,[url](QObject* obj, const QUrl& objUrl) {if (!obj && url == objUrl)QCoreApplication::exit(-1);},Qt::QueuedConnection);engine.load(url);return app.exec();
}

属性树形控件

属性树形控件的类虽然已经被移除,但QtCreator还是在使用的,可以通过Qt的源码查找qtpropertybrowser
展示使用的代码:
TestData1.h

#ifndef TESTDATA1_H
#define TESTDATA1_H#include <QColor>
#include <QObject>class TestData1 : public QObject {Q_OBJECTQ_PROPERTY(int count READ count WRITE setCount NOTIFY countChanged FINAL)Q_PROPERTY(QColor bkColor READ bkColor WRITE setBkColor NOTIFY bkColorChanged FINAL)Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged FINAL)
public:explicit TestData1(QObject* parent = nullptr): QObject(parent){}int count() const { return m_count; }void setCount(int value){if (value != m_count) {m_count = value;emit countChanged(m_count);}}QColor bkColor() const { return m_bkColor; }void setBkColor(const QColor& color){if (color != m_bkColor) {m_bkColor = color;emit bkColorChanged(m_bkColor);}}QString name() const { return m_name; }void setName(const QString& value){if (value != m_name) {m_name = value;emit nameChanged(m_name);}}signals:void countChanged(int value);void bkColorChanged(const QColor& color);void nameChanged(const QString& name);private:int m_count { 1 };QColor m_bkColor { Qt::black };QString m_name { "TestData" };
};#endif // TESTDATA1_H

TestData2.h

#ifndef TESTDATA2_H
#define TESTDATA2_H#include <QColor>
#include <QObject>class TestData2 : public QObject {Q_OBJECTQ_PROPERTY(int number READ count WRITE setCount NOTIFY countChanged FINAL)Q_PROPERTY(QColor bkColor READ bkColor WRITE setBkColor NOTIFY bkColorChanged FINAL)Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged FINAL)
public:explicit TestData2(QObject* parent = nullptr): QObject(parent){}int count() const { return m_number; }void setCount(int value){if (value != m_number) {m_number = value;emit countChanged(m_number);}}QColor bkColor() const { return m_bkColor; }void setBkColor(const QColor& color){if (color != m_bkColor) {m_bkColor = color;emit bkColorChanged(m_bkColor);}}QString name() const { return m_name; }void setName(const QString& value){if (value != m_name) {m_name = value;emit nameChanged(m_name);}}signals:void countChanged(int value);void bkColorChanged(const QColor& color);void nameChanged(const QString& name);private:int m_number { 99 };QColor m_bkColor { Qt::blue };QString m_name { "TestData2" };
};
#endif // TESTDATA2_H

widget.h

#ifndef WIDGET_H
#define WIDGET_H#include "qtpropertybrowser/qteditorfactory.h"
#include "qtpropertybrowser/qttreepropertybrowser.h"
#include "qtpropertybrowser/qtvariantproperty.h"
#include "testdata1.h"
#include "testdata2.h"
#include <QListWidgetItem>
#include <QWidget>QT_BEGIN_NAMESPACE
namespace Ui {
class Widget;
}
QT_END_NAMESPACEclass Widget : public QWidget {Q_OBJECTpublic:Widget(QWidget* parent = nullptr);~Widget();private slots:void on_listWidget_currentItemChanged(QListWidgetItem* current, QListWidgetItem* previous);protected:void updateProperties(QObject* selectedObject);private:Ui::Widget* ui;QtVariantPropertyManager* m_variantManager;QtVariantEditorFactory* m_variantFactory;TestData1 m_data1;TestData1 m_data1_2;TestData2 m_data2;TestData2 m_data2_2;
};
#endif // WIDGET_H

widget.cpp

#include "widget.h"
#include "./ui_widget.h"
#include <QMetaProperty>Widget::Widget(QWidget* parent): QWidget(parent), ui(new Ui::Widget), m_variantManager(new QtVariantPropertyManager(this)), m_variantFactory(new QtVariantEditorFactory(this))
{ui->setupUi(this);m_data1.setName("TestData1");m_data1.setBkColor(Qt::red);m_data1_2.setName("TestData1_2");m_data1_2.setBkColor(Qt::yellow);m_data2.setCount(60);ui->widget->setFactoryForManager(m_variantManager, m_variantFactory);QtVariantProperty* item = m_variantManager->addProperty(QVariant::Double, "透明度");item->setValue(0.5);ui->widget->addProperty(item);
}Widget::~Widget()
{delete ui;
}void Widget::on_listWidget_currentItemChanged(QListWidgetItem* current, QListWidgetItem* previous)
{static int index = 0;int i = index % 4;if (i == 0)updateProperties(&m_data1);else if (i == 1)updateProperties(&m_data1_2);else if (i == 2)updateProperties(&m_data2);elseupdateProperties(&m_data2_2);index++;
}void Widget::updateProperties(QObject* selectedObject)
{ui->widget->clear();auto metaObj = selectedObject->metaObject();for (int i = 0; i < metaObj->propertyCount(); ++i) {auto propertyObj = metaObj->property(i);auto propertyName = propertyObj.name();auto propertyValue = selectedObject->property(propertyName);auto item = m_variantManager->addProperty(propertyValue.type(), propertyName);item->setValue(propertyValue);ui->widget->addProperty(item);connect(m_variantManager, &QtVariantPropertyManager::valueChanged,this, [selectedObject](QtProperty* prop, const QVariant& value) {selectedObject->setProperty(prop->propertyName().toStdString().c_str(), value);});}
}

注意
ui->widget就是QtTreePropertyBrowser类。

效果:
image.png

总结

属性系统说到底就是建立在类的成员变量之上,并通过标准化的接口(getter和setter)以及元对象系统来增强这些成员的功能。
在很多UI和数据进行交互的时候,我们通常可以通过属性系统提供的标准化接口来实现,因为其带来以下好处:

  1. 一致性和易用性

提供一个统一的方法来访问和修改对象的属性,这意味着无论你在哪里或如何使用这些对象,访问和修改属性的方式总是相同的。这种一致性减少了学习和使用不同对象时的认知负担,使得开发更直观,同时也减少了代码中可能出现的错误。

  1. 封装和数据保护

通过标准化的 getter 和 setter 方法来访问和修改数据,这增强了封装性,保护了数据不被非法访问和修改。封装是面向对象编程中的一个核心概念,它隐藏了对象的内部状态和实现细节,只暴露有限的接口与外界交互。

  1. 解耦和模块化

标准化接口促进了解耦和模块化设计。开发者可以更轻松地替换或修改内部实现而不影响使用这些对象的代码。这是因为外部代码依赖于接口而非具体实现,从而使得整个系统更加灵活和可维护。

  1. 自动化工具和库的集成

标准化的属性接口使得自动化工具(如 GUI 设计器)和其他库(如序列化库、数据库映射工具)能够更容易地与你的代码集成。例如,一个自动化工具可以通过反射机制查找所有属性并允许用户在图形界面中配置它们。

  1. 动态性和反射能力

Qt 的元对象系统依赖于标准化的属性接口来支持反射,即在运行时查询和操作对象的能力。这不仅有助于开发各种动态特性(如动态数据绑定和脚本集成),也使得开发者能够编写通用代码来处理不同类型的对象。

  1. 跨语言和平台兼容性

标准化接口简化了在不同编程语言和平台间的对象交互。由于接口提供了清晰定义的交互点,因此可以更容易地将 Qt 应用与其他系统集成,或者在不同平台间迁移应用。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/33343.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

WPF——属性

一、属性 类最初只有字段与函数&#xff0c;字段为一个变量&#xff0c;访问权限可以是private&#xff0c;protected&#xff0c;public。而将字段设为private&#xff0c;不方便外界对类数据的操作&#xff0c;但是将字段设为public又怕外界对数据进行非法操作&#xff0c;于…

尴尬时刻:如何在忘记名字时巧妙应对

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

llama系列模型学习

一、目录 llama1 模型与transformer decoder的区别llama2 模型架构llama2 相比llama1 不同之处llama3 相比llama2 不同之处llama、llama2、llama3 分词器词表大小以及优缺点采用的损失函数是什么&#xff1f;为什么Layer Norm 改为RMS Norm?如何消除模型幻觉&#xff1f; 二…

Vscode配置Verilog开发环境(二)

两年前我写过一篇vscode配置fpga开发环境的博客&#xff0c;近期发现查看的人数还是比较多的&#xff0c;经过两年的使用&#xff0c;优化了代码模板以及删减了一些插件等等&#xff0c;因此有了本文。 目前我的vscode只有下图中的九个插件&#xff0c;相比前文已经减少了一些了…

地级市绿色创新及碳排放与环境规划数据(2000-2021年)

数据简介&#xff1a;分享各个城市对于碳排放的降低做出了哪些共享。该数据是地级市2000-2021年间由绿色创新、碳排放与环境规制数据构成的能源与环境研究数据大合集&#xff0c;并对其进行可视化处理&#xff0c;供大家研究使用。当今我国大力推进生态文明建设、美丽中国建设等…

【日常开发之Windows共享文件】Java实现Windows共享文件上传下载

文章目录 Windows 配置代码部分Maven代码 Windows 配置 首先开启服务&#xff0c;打开控制面板点击程序 点击启用或关闭Windows功能 SMB1.0选中红框内的 我这边是专门创建了一个用户 创建一个文件夹然后点击属性界面&#xff0c;点击共享 下拉框选择你选择的用户点击添加…

python爬虫必须要高匿IP吗 ?

各个平台搜索关键词就可以找到不同品牌的代理IP了。 找倒是不难&#xff0c;难的是能从中挑选出靠谱且合适的代理品牌。 在选择代理IP这块&#xff0c;不建议使用免费和超低价的代理&#xff0c;因为这一类的代理往往非常不稳定&#xff0c;很多都被网站拉黑过&#xff0c;特…

PS添加物体阴影

一、选择背景&#xff0c;确保物体和北京分割出图层 二、右键单击物体图层&#xff0c;点击混合选项&#xff0c;点击投影 三、调整参数&#xff0c;可以看效果决定(距离是高度&#xff0c;扩展是浓度&#xff0c;大小是模糊程度)&#xff0c;保存即可

实用软件下载:CrossOver 2024最新安装包及详细安装教程

​根据软件大数据显示上传或者手动输入软件都非常简单&#xff0c;一般来说CrossOver会自动连接到一个Win文件共享服务器&#xff08;Samba或CIFS&#xff09;上&#xff0c;使用者能够直接在这个服务器中选择并上传软件执行文件。实际上我们可以这样讲调整CrossOver设置&#…

汽车数据应用构想(五)

如果说路面信息&#xff08;POI&#xff09;可以通过采集车、人工等方式来获取&#xff0c;用户习惯可以通过手机生态应用来获取&#xff0c;那么车的信息应该算是车辆独有的垂直领域价值了&#xff0c;它是实实在在只有车厂才拥有的数据财富&#xff0c;任何互联网大厂都抢不走…

如何使用mapXplore将SQLMap数据转储到关系型数据库中

关于mapXplore mapXplore是一款功能强大的SQLMap数据转储与管理工具&#xff0c;该工具基于模块化的理念开发&#xff0c;可以帮助广大研究人员将SQLMap数据提取出来&#xff0c;并转储到类似PostgreSQL或SQLite等关系型数据库中。 功能介绍 当前版本的mapXplore支持下列功能…

一条Redis命令是如何执行的?

一条Redis命令是如何执行的&#xff1f; 源码结构核心数据结构redisServerredisClientredisDbredisObjectaeEventLoop 核心流程redis启动流程main() 主循环aeEventProcess执行过程命令执行的流程过程1&#xff08;redis启动&#xff09;过程2&#xff08;客户端与服务端建立链接…

【Android】Android Studio 使用Kotlin写代码时代码提示残缺问题解决

问题描述 Android Studio升级之后&#xff0c;从Android Studio 4.2升级到Android Studio Arctic Fox版本&#xff0c;因为项目比较老&#xff0c;使用的Gradle 版本是3.1.3&#xff0c;这个版本的Android Studio最低支持Gradle 3.1版本&#xff0c;应该算是比较合适的版本。 …

不翻墙安装yolov8环境下的RT-DETR并实现PCB表面缺陷检测

目录 一、新建conda环境二、安装yolov8环境1.克隆安装包2.安装依赖包3.测试模型 任务2&#xff1a;基于RT-DETR实现PKU-PCB表面缺陷检测数据准备 数据增强测试 总结 一、新建conda环境 创建并激活conda环境&#xff1a; 在conda创建一个名为yolov8的新环境&#xff0c;并在其中…

国际网络专线的开通流程

1. 选择服务商&#xff1a;首先&#xff0c;您需要选择一个可靠的服务商来提供国际网络专线服务。确保服务商具有良好的声誉和专业知识&#xff0c;以便为您提供高质量的网络连接和支持。 2. 评估需求&#xff1a;在与服务商沟通之前&#xff0c;您需要明确自己的网络需求。这…

dp经典问题:LCS问题

dp&#xff1a;LCS问题 最长公共子序列&#xff08;Longest Common Subsequence, LCS&#xff09;问题 是寻找两个字符串中最长的子序列&#xff0c;使得这个子序列在两个字符串中出现的相对顺序保持一致&#xff0c;但不要求连续。 力扣原题链接 1.定义 给定两个字符串 S1…

猫狗识别—视频识别

猫狗识别—视频识别 1. 导入所需的库&#xff1a;2. 创建Tkinter主窗口并设置标题&#xff1a;3. 设置窗口的宽度和高度&#xff1a;4. 创建一个Canvas&#xff0c;它将用于显示视频帧&#xff1a;5. 初始化一个视频流变量cap&#xff0c;用于存储OpenCV的视频捕获对象&#xf…

【速速收藏】适用于Linux系统的五个优秀PDF编辑器

PDF (Portable Document Format) 是便携文档格式的缩写&#xff0c;这是一种用于电子共享文档的标准格式&#xff0c;广泛应用于各种文档类型的存储和分发。然而&#xff0c;有时我们可能需要对PDF文档进行更改和编辑。本文将介绍五款在Linux平台上广受欢迎的PDF编辑器。 ​​…

陀螺仪LSM6DSV16X与AI集成(8)----MotionFX库解析空间坐标

陀螺仪LSM6DSV16X与AI集成.8--MotionFX库解析空间坐标 概述视频教学样品申请源码下载开启CRC串口设置开启X-CUBE-MEMS1设置加速度和角速度量程速率选择设置FIFO速率设置FIFO时间戳批处理速率配置过滤链初始化定义MotionFX文件卡尔曼滤波算法主程序执行流程lsm6dsv16x_motion_fx…

【分布式事务】Seata AT实战

目录 Seata 介绍 Seata 术语 Seata AT 模式 介绍 实战&#xff08;nacos注册中心&#xff0c;db存储&#xff09; 部署 Seata 实现 RM 实现 TM 可能遇到的问题 1. Seata 部署成功&#xff0c;服务启动成功&#xff0c;全局事务不生效 2. 服务启动报错 can not get …