Skip to content

属性系统

属性系统:基于元对象系统,通过信号槽提供对象间的通信,不依赖于编译器。
QObject 的 setProperty 和 property 用于通过属性名称动态设置和获取属性值。其实主要实现 c++ 和 qml 交互。
由于元对象系统的特点,这就保证了 Qt 属性系统是独立于编译器和平台的。不仅如此,我们还可以使用 Q_PROPERTY() 来定义编译器的静态属性。
使用 Q_PROPERTY 宏定义的属性,就是静态属性。后期使用 setProperty() 动态添加的属性,就是动态属性。

属性操作方式:

  • 可使用 QObject::propertyQObject::setProperty 函数进行存取
  • 若属性有相关联的存取函数,则可使用存取函数进行存取
  • 属性还可通过元对象系统的 QMetaObject 类进行存取

Q_PROPERTY 宏:
Q_PROPERTY() 宏用来定义可通过元对象系统访问的属性,通过它定义的属性,可以在 QML 中访问、修改,也可以在属性变化时发射特定的信号。
Q_PROPERTY() 宏定义一个返回值类型为 type,名称为 name 的属性,用 READ、WRITE 关键字定义属性的读取、写入函数,还有其他的一些关键字定义属性的一些操作特性。属性的类型可以是 QVariant 支持的任何类型,也可以用户自定义类型。

宏参数说明:

  • READ:用于读取属性值,如果未指定成员变量(通过 MEMBER),则需要读取访问器函数。
  • WRITE:写访问器函数是可选的。用于设置属性值。它必须返回 void,并且必须只接受一个参数,要么是属性的类型,要么是指向该类型的指针或引用。
  • MEMBER:如果未指定读取访问器函数,则需要成员变量关联。这使得给定的成员变量可读写,而无需创建读写访问器函数。如果需要控制变量访问,除了成员变量关联(但不是两者)之外,还可以使用读取访问器函数。
  • RESET:复位功能是可选的。它用于将属性设置回其特定于上下文的默认值。
  • NOTIFY:通知信号是可选的。如果已定义,它应该指定该类中的一个现有信号,该信号在属性值更改时发出。成员变量的通知信号必须采用零个或一个参数,这些参数必须与属性的类型相同。参数将采用属性的新值。仅当属性确实发生更改时才应发出 NOTIFY 信号,以避免绑定在 QML 中被不必要地重新计算。

语法示例:

cpp
Q_PROPERTY(属性类型 属性名 READ 读函数名 WRITE 写入函数名 NOTIFY 信号函数)

使用 QObjectproperty() 方法访问属性,如果该属性定义了 WRITE 方法,还可以使用 setProperty() 修改属性。
Q_PROPERTY 用于 c++ 类注册到 qml 交互上。在 c++ 的变化发送信号,而在 qml 上接收信号,实现处理槽函数。


对象树

对象树和父子关系

在 Qt 中,对象树和父子关系是内存管理和对象组织的基础机制。

对象树

对象树(Object Tree)是 Qt 中用于管理 Qobject 及其子类实例的一种层次结构。

对象树的组成
  • QObject:对象树中的节点,可以是任何继承自 QObject 的类,如 QWidget、QThread、QTimer 等。
  • 父子关系:对象树中的节点通过父子关系连接。每个 QObject 可以有多个子 QObject,但每个子 QObject 只能有一个父 QObject。
对象树的特点
  • 自动管理:对象树中的子对象会随着其父对象的销毁而自动销毁,这有助于防止内存泄漏。当父对象被删除时,它会遍历其所有子对象,并依次删除它们。这种自动管理机制大大简化了内存管理的复杂性,开发者无需手动跟踪和释放每个子对象的内存。
  • 层次结构:对象树反映了对象之间的层次关系,这对于图形用户界面(GUI)应用程序尤其重要,其中窗口和控件通常以层次结构组织。例如,一个主窗口可能包含多个子窗口或控件,这些子窗口和控件又可能包含自己的子控件,形成一个清晰的层次结构。
  • 生命周期管理:对象树简化了对象生命周期的管理,因为子对象的生命周期与其父对象紧密相关。这意味着只要父对象存在,子对象就会存在;当父对象被销毁时,子对象也会被自动销毁。

对象树的操作

添加父对象

创建一个 QObject 时,可以将其作为另一个 QObject 的子对象,通过在构造函数中传递父对象的指针来实现。

cpp
#include <QObject>
#include <iostream>

class Father : public QObject
{
    Q_OBJECT
public:
    Father(QObject *parent = nullptr) :QObject(parent) {}
    ~Father(){std::cout << "父类被析构了" << std::endl;}
};

class Son : public QObject
{
    Q_OBJECT
public:
    Son(QObject *parent = nullptr) :QObject(parent) {} // 通过构造函数增加父对象
    ~Son(){std::cout << "子类被析构了" << std::endl;}
}

// 使用示例
Father *f = new Father;
Son *s = new Son;
s->setParent(f); // 通过 setParent 增加父对象
移除父对象
cpp
Father *f = new Father;
Son *s = new Son;
s->setParent(f); // 通过 setParent 增加父对象
s->setParent(nullptr); // 通过 setParent 设置父对象为nullptr 来移除父对象
查询子对象
cpp
Father *f = new Father;
Son *s = new Son;
s->setParent(f); // 通过 setParent 增加父对象
f->children(); // 获取所有的子对象
获取父对象
cpp
Father *f = new Father;
Son *s = new Son;
s->setParent(f); // 通过 setParent 增加父对象
// 获取父对象
qobject *father = s->parent();

注意事项

  • 当一个 QObject 被删除时,它会发出 destroyed() 信号,但不会在析构函数中发出,因为那时对象已经不存在了。
  • 在某些情况下,可能需要手动管理 Qobject 的生命周期,这时可以通过调用 setParent(nullptr) 来移除对象的父子关系。
  • 对象树不适用于非 Qobject 类型的对象,这些对象需要手动管理内存。

父子类关系

在 Qt 中,对象之间的父子关系是非常重要的概念,它对于对象的内存管理和事件传递有着重要的影响。

父子关系的影响:
  • 当父对象被销毁时,它的所有子对象也会被自动销毁。这是 Qt 中自动内存管理的一个重要特性,大大减少了内存泄漏的风险。
  • 父对象的位置和大小变化也会影响到子对象。例如,如果父窗口的大小发生变化,其子窗口和控件的位置和大小可能会根据布局策略进行相应的调整。
父子关系的适用范围:
  • 父子关系通常在 GUI 编程中用得比较多,例如窗口和窗口上的控件之间建立父子关系。这样可以方便地管理控件的显示和隐藏,以及在窗口大小变化时进行自动布局调整。
  • 父子关系对对象的内存管理方面非常有用,能够简化内存释放的过程。特别是在复杂的 GUI 应用中,有大量的控件和对象需要管理,父子关系可以有效地避免内存泄漏。
注意事项:
  • 每个 Qobject 只能有一个父对象:一个 Qobject 不能同时是多个 Qobject 的子对象。如果尝试为一个 Qobject 设置多个父对象,将会导致运行时错误。
  • 根对象:对象树中的根对象没有父对象。通常,应用程序的主窗口或主要的控制对象可以作为根对象。
  • 删除顺序:当删除一个 qobject 时,它的所有子对象将按照它们被创建的相反顺序被删除。这确保了在删除过程中,子对象的析构函数能够正确处理与父对象相关的资源和状态。

知识如风,常伴吾身