写在前面
Qt 是一个建立在 C++ 之上的框架,它扩展了 C++ 的功能,使得开发者可以更容易地开发跨平台的应用程序。Qt 的设计哲学和功能集都与 C++ 紧密相关,但同时也提供了比纯 C++ 更高的生产力和更丰富的功能。
Qt如何在C++基础上扩展的
这里新建一个C++项目和QWidget项目比较。
自动生成的文件及代码比较:
属性页比较:
可以看到创建的Qt项目比标准C++项目多了了几行代码,多了几个Qt相关的配置。
多出的代码如下:
QApplication a(argc, argv); //创建一个唯一的QApplication对象
QObjectTest w; //创建了一个QWidget对象
w.show(); //显示QWidget对象
return a.exec(); //进入唯一的QApplication对象的消息循环
可以看到创建Qt项目时,会自动创建一个QApplication对象和一个窗体对象,然后显示这个窗体并进入桌面应用都必须要有的消息循环。
Qt项目多出的配置如下:
可以看到多出的配置有:
Qt Project Settings(同.pro文件),Qt项目相关配置
Qt Meta-Object Complier: Qt元对象编译器moc,用来编译所有带Q_OBJECT宏的类所属的源文件。
Qt Resource Complier: Qt资源对象编译器rcc,用来编译Qt资源文件.qrc。这里还包含了Qt Quick Complier编译器,用来编译QML,这里以QWidget为主,不作讨论。
Qt User Interface Complier: Qt用户界面编译器uic,用来编译用户界面文件.ui。
这几个编译器是何时作用的,这里编译项目看编译输出:
至此可以知道Qt在C++项目的编译编译流程中添加了uic、rcc、moc等编译器的编译,如图:
普通C++类使用Qt核心特性
要想使普通C++类附带Qt的核心特性,需满足:
①必须在Qt开发环境(即Qt项目)中
②使该类继承QObject
③声明Q_OBJECT宏
这里创建一个C++类CppTest:
重新生成,可以看到直接到编译步骤,未涉及Qt相关的编译:
添加QObject类继承以及Q_OBJECT宏后,重新编译:
注意:继承QObject、添加Q_OBJECT宏缺一不可!!!
单独继承QObject而不声明Q_OBJECT宏,不会进入Qt相关编译流程:
单独声明Q_OBJECT宏,而不继承Q_Object,编译报错:
C++类继承QObject并添加Q_OBJECT宏后,就可以在该类中使用Qt的动态属性、GUI、信号和槽、模型/视图、插件、绘图、动画等核心特性了。
标准C++项目无法直接转成Qt项目
无法在VS中通过配置设置添加Qt环境!!!
无法在VS中通过配置设置添加Qt环境!!!
无法在VS中通过配置设置添加Qt环境!!!
即属性中只能添加Qt相关的头文件和库,无法改变当前项目的编译过程(无法添加moc元对象编译器、uic用户界面文件编译器、qrc资源文件编译器及相应的配置选项)。
QObject提供的一些扩展应用
动态属性
Qt的动态属性(Dynamic Properties)允许开发者在运行时为对象添加额外的属性,这些属性并不需要在对象的类定义中预先声明。动态属性的机制使得Qt的QObject
派生类能够具有更加灵活的属性管理方式,它们可以用于存储配置信息、用户偏好设置、状态信息等。
以下是使用Qt动态属性的一些关键点:
- 注册属性: 使用
Q_CLASSINFO
宏在类定义中注册动态属性的名称。这步是可选的,主要用于为属性提供元数据。
Q_CLASSINFO("dynamicPropertyName", "defaultValue")
- 设置属性**: 使用
QObject::setProperty()
方法为对象设置动态属性。
myObject.setProperty("dynamicPropertyName", QVariant(value));
- 获取属性: 使用
QObject::property()
方法获取对象的动态属性。
QVariant value = myObject.property("dynamicPropertyName");
- 监听属性变化: 可以通过连接
QObject::propertyChanged()
信号来监听动态属性的变化。
connect(&myObject, &QObject::propertyChanged, this, &MyClass::onPropertyChanged);
- 删除属性**: 使用
QObject::setProperty()
方法并传入nullptr
作为值,可以删除动态属性。
myObject.setProperty("dynamicPropertyName", QVariant());
- 属性的类型**: 动态属性的值通常是
QVariant
类型,这意味着它可以存储多种类型的数据。 - 动态属性的作用域**: 动态属性是与对象实例绑定的,它们不是类级别的,也不是静态的。
- 使用场景**: 动态属性适用于需要在运行时添加或修改属性的场景,例如插件架构、脚本绑定、用户配置等。
下面是一个简单的示例代码,展示了如何在Qt中使用动态属性:
#include <QObject>
#include <QVariant>
#include <QDebug>
class MyObject : public QObject
{
Q_OBJECT
public:
MyObject(QObject *parent = nullptr) : QObject(parent) {
// 可以在这里设置默认的动态属性
}
void setDynamicProperty(const QString &name, const QVariant &value) {
setProperty(name.toUtf8().constData(), value);
}
QVariant getDynamicProperty(const QString &name) const {
return property(name.toUtf8().constData());
}
};
int main() {
MyObject obj;
// 设置动态属性
obj.setDynamicProperty("username", "JohnDoe");
obj.setDynamicProperty("age", 30);
// 获取动态属性
qDebug() << "Username:" << obj.getDynamicProperty("username").toString();
qDebug() << "Age:" << obj.getDynamicProperty("age").toInt();
return 0;
}
Q_PROPERTY
在Qt中,Q_PROPERTY
宏是一种强大的机制,它允许开发者在Qt类的声明中定义属性,这些属性可以具有读写权限、只读权限或只写权限,并可以指定属性的类型、名称和额外的元数据。使用Q_PROPERTY
宏定义的属性可以被Qt的属性系统自动管理,并且可以很方便地与Qt的信号和槽机制、属性绑定、以及序列化等功能集成。
Q_PROPERTY
的使用格式如下:
Q_PROPERTY(type name
READ getFunction
[WRITE setFunction]
[RESET resetFunction]
[NOTIFY notifySignal]
[REVISION int]
[DESIGNABLE bool]
[SCRIPTABLE bool]
[STORED bool]
[USER bool]
[CONSTANT]
[FINAL])
这里的选项可以根据需要进行选择:
- type和name:属性的类型和名称。
- READ、WRITE和RESET:用于读取、写入和重置属性的成员函数。
- NOTIFY:当属性发生变化时,会发出的信号。
- REVISION、DESIGNABLE、SCRIPTABLE、STORED、USER、CONSTANT和FINAL:一些用于修改属性特性的选项。
下面是一个简单的示例:
class MyClass : public QObject
{
Q_OBJECT
Q_PROPERTY(QString myProperty READ myProperty WRITE setMyProperty NOTIFY myPropertyChanged)
public:
MyClass(QObject *parent = nullptr) : QObject(parent) {}
QString myProperty() const { return m_myProperty; }
void setMyProperty(const QString &value)
{
if (value != m_myProperty) {
m_myProperty = value;
emit myPropertyChanged();
}
}
signals:
void myPropertyChanged();
private:
QString m_myProperty;
};
动态属性和Q_PROPERTY的区别
Q_PROPERTY`和动态属性(Dynamic Properties)是Qt中用于处理对象属性的两种不同机制,它们各有特点和适用场景。以下是它们之间的主要区别:
- 定义方式:
Q_PROPERTY
: 属性是在类的头文件中通过宏定义声明的,需要编译时确定,并且需要相应的getter和setter函数。- 动态属性: 属性是在运行时通过
QObject::setProperty()
和QObject::property()
方法动态添加和查询的,不需要在类定义中预先声明。
- 类型安全:
Q_PROPERTY
: 提供类型安全,属性的类型在编译时就已经确定,并且可以通过Qt的类型系统进行类型检查。- 动态属性: 使用
QVariant
作为属性值的容器,类型检查是在运行时进行的。
- 元数据:
Q_PROPERTY
: 可以利用元数据提供额外的信息,如属性的描述、默认值、设计able属性等。- 动态属性: 通常不包含元数据信息,但可以通过其他方式进行管理。
- 信号和槽:
Q_PROPERTY
: 可以很容易地发出属性改变的信号,并且可以连接到槽函数,实现属性变化的自动通知。- 动态属性: 属性变化通常不会自动发出信号,需要手动管理通知机制。
- 设计和使用场景:
Q_PROPERTY
: 适用于需要明确定义属性,并且需要利用Qt属性系统提供的功能,如属性绑定、序列化等的场景。- 动态属性: 适用于需要在运行时动态添加或删除属性,或者属性数量和类型在编译时不确定的场景。
- 性能:
Q_PROPERTY
: 由于属性系统高度优化,通常具有较好的性能。- 动态属性: 性能可能会略低于
Q_PROPERTY
,因为它需要在运行时处理属性的存储和检索。
- 兼容性:
Q_PROPERTY
: 可以与Qt Designer等工具集成,支持属性编辑和界面布局。- 动态属性: 不直接支持Qt Designer等工具的集成。
8.生命周期:Q_PROPERTY
的生命周期与类的生命周期相同,通常用于静态属性;动态属性的生命周期是在它被设定到获取删除的过程中,使用者可以在需要时添加或删除。
总结来说,Q_PROPERTY
宏提供了一种声明式的方式来定义属性,它具有类型安全、元数据支持、易于集成信号和槽等优点,适用于属性明确且需要利用Qt属性系统的场景。而动态属性则提供了一种灵活的方式来在运行时管理属性,适用于属性数量和类型不确定或需要动态变化的场景.
总结
本文从0开始新建C++和Qt项目,以此展开Qt是如何在C++基础上扩展的。
Qt项目较C++项目的编译流程中添加了moc、uic、qrc编译以支持Qt扩展。
最后简单介绍了Qt的动态属性和Q_PROPERTY两个在C++基础上扩展的核心特性。
像其他的GUI、信号和槽、模型/视图、插件、绘图、动画等核心特性,实际应用较多,这里不再赘述。