QT:核心机制
元对象系统
●该类必须继承自QObject类。
●必须在类定义的私有部分添加Q_OBJECT宏(在类定义时,如果没有指定public或者private关键字,则默认为private)。
●元对象编译器Meta-ObjectCompiler(MOC)为QObject的子类实现元对象特性提供必要的代码。
提供对象通信机制:信号(Signals)和槽(Slots)。这是一种观察者模式的实现,允许对象之间在不直接引用的情况下进行通信。
运行时类型信息:允许在运行时查询对象的类型信息。
动态属性系统:允许在运行时动态地添加和查询对象的属性。
属性系统
在Qt中,Q_PROPERTY()宏是用于在类中声明属性的。这个宏允许你在Qt的元对象系统中注册一个属性,从而使得该属性可以利用Qt的信号与槽机制、动态属性系统以及Qt Designer等工具进行交互。
Q_PROPERTY()宏通常放在类的私有部分,紧跟在Q_OBJECT宏之后。它接受多个参数,用于定义属性的名称、类型、读写函数(可选)、通知信号(可选)以及属性存储(可选)。
以下是一个Q_PROPERTY()宏的基本用法示例:
class MyClass : public QObject
{
Q_OBJECT
Q_PROPERTY(int myProperty READ getMyProperty WRITE setMyProperty NOTIFY myPropertyChanged)
public:
MyClass(QObject *parent = nullptr) : QObject(parent), m_myProperty(0) {}
int getMyProperty() const {
return m_myProperty;
}
void setMyProperty(int value) {
if (m_myProperty != value) {
m_myProperty = value;
emit myPropertyChanged(value);
}
}
signals:
void myPropertyChanged(int newValue);
private:
int m_myProperty;
};
在这个例子中:
int myProperty:定义了属性的类型为int,名称为myProperty。
READ getMyProperty:指定了读取该属性的函数是getMyProperty。
WRITE setMyProperty:指定了写入该属性的函数是setMyProperty。
NOTIFY myPropertyChanged:指定了当属性值改变时应该发出的通知信号是myPropertyChanged。
当你使用Q_PROPERTY()宏声明了一个属性后,你可以通过Qt的反射机制(如QObject::property()和QObject::setProperty())来动态地获取和设置该属性的值。此外,由于属性已经注册到了元对象系统中,因此它还可以与Qt的信号与槽机制、Qt Designer的属性编辑器以及Qt的动画框架等进行交互。
需要注意的是,虽然Q_PROPERTY()宏提供了很大的灵活性,但它也有一些限制。例如,它不支持直接声明为const的属性,因为属性需要能够被读取和写入(或者至少被读取)。此外,虽然你可以为属性指定默认值,但这个默认值只能在对象构造时通过QObject::setProperty()来设置,而不能在Q_PROPERTY()宏中直接指定。
对象树与拥有权
Qt中使用对象树(object tree)来组织和管理所有的QObject类及其子类的对象。当创建一个QObject对象时,如果使用了其他的对象作为其父对象(parent),那么这个对象就会被添加到父对象的children()列表中,这样当父对象被销毁时,这个对象也会被销毁
例子描述
假设我们有一个简单的Qt应用程序,其中包含一个主窗口(MainWindow)和几个子部件,如按钮(QPushButton)和标签(QLabel)。在这个例子中,我们将展示如何使用对象树来组织这些对象。
#include <QApplication>
#include <QMainWindow>
#include <QPushButton>
#include <QLabel>
#include <QVBoxLayout>
class MainWindow : public QMainWindow {
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr) : QMainWindow(parent) {
QWidget *centralWidget = new QWidget(this); // 创建中心部件,并设置为主窗口的中心部件
QVBoxLayout *layout = new QVBoxLayout(centralWidget); // 创建垂直布局
QLabel *label = new QLabel("Hello, Qt!", this); // 创建标签,并设置MainWindow为其父对象(但实际上这里应该设置为centralWidget,下面会修正)
label->setParent(centralWidget); // 显式设置父对象为centralWidget,以将其添加到布局中
layout->addWidget(label); // 将标签添加到布局中
QPushButton *button = new QPushButton("Close", this); // 创建按钮,并错误地设置MainWindow为其父对象(同样应该设置为centralWidget)
button->setParent(centralWidget); // 修正父对象为centralWidget
connect(button, &QPushButton::clicked, this, &QMainWindow::close); // 连接按钮的点击信号到主窗口的关闭槽
layout->addWidget(button); // 将按钮添加到布局中
setCentralWidget(centralWidget); // 设置中心部件
}
};
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
MainWindow mainWindow; // 创建主窗口对象(在栈上分配内存,不需要手动delete)
mainWindow.show(); // 显示主窗口
return app.exec(); // 进入Qt的事件循环
}
说明
父对象设置:在上面的代码中,虽然标签(QLabel)和按钮(QPushButton)最初被错误地设置为以MainWindow为父对象,但通过调用setParent(centralWidget)进行了修正。实际上,在Qt中,通常将布局或容器部件(如QWidget)设置为子部件的父对象,以便更好地管理它们的生命周期和布局。
对象树结构:在这个例子中,MainWindow是顶层对象(没有父对象),而centralWidget是MainWindow的子对象。标签和按钮则是centralWidget的子对象。这样,就形成了一个对象树结构。
内存管理:当MainWindow被销毁时(例如,当用户关闭主窗口时),由于对象树机制,centralWidget及其所有子对象(包括标签和按钮)也会被自动销毁。这简化了内存管理,并防止了内存泄漏。
信号与槽:在这个例子中,还展示了如何使用Qt的信号与槽机制来连接按钮的点击信号到主窗口的关闭槽。这也是Qt对象模型的一个重要特性。