附加属性由一个提供者类型为另一个对象提供,例如 ListView.isCurrentItem、Layout.fillWidth。附加信号处理器则监听相应附加对象的事件,例如 Keys.onReturnPressed、Component.onCompleted。它们不是自动出现在所有对象上的全局变量,也不会因为写在父项上,就自动代表所有子项的状态。
本文以 Qt 6.10 为基线。内置例子可以保存为独立 QML 文件,由 Qt Quick 窗口承载;C++ 例子给出完整的注册与构建文件。
委托内部应明确访问谁的附加对象
跳转到“委托内部应明确访问谁的附加对象”TypeName.propertyName 是附加属性的限定写法;TypeName.onSignalName 是附加信号处理器。它们所附加的接收对象是语义的一部分。例如 ListView.isCurrentItem 要在委托根对象上判断是否为当前项;位于委托中的另一个矩形或文本可以有自己的附加对象,不能把它的裸名称当成根委托的状态。
保存为 AttachedDelegate.qml。按回车增加计数,列表的当前行用红色显示;文本显式访问 delegateItem.ListView.isCurrentItem。
import QtQuick
Item { id: root width: 280 height: 240 focus: true property int returnCount: 0
Keys.onReturnPressed: function(event) { root.returnCount += 1 event.accepted = true }
ListView { id: view width: parent.width height: 180 model: 3 currentIndex: 0 delegate: Item { id: delegateItem required property int index width: view.width height: 40
Text { anchors.centerIn: parent text: "Item " + delegateItem.index color: delegateItem.ListView.isCurrentItem ? "red" : "black" } MouseArea { anchors.fill: parent onClicked: { view.currentIndex = delegateItem.index root.forceActiveFocus() } } } }
Text { anchors.top: view.bottom text: "Return pressed: " + root.returnCount }}focus 在这里仍是 Item 的普通属性;Keys 才是附加处理的提供者。键盘事件还要求窗口激活和项目拥有活动焦点。与此相似,Layout.fillWidth 是布局读取的附加信息,它不是对任意脱离布局的项目都生效的“自动填充”命令。
Component 内的实例才接收完成事件
跳转到“Component 内的实例才接收完成事件”Component 可以保存稍后实例化的对象定义,但 Component { ... } 在 id 之外只容纳一个顶层对象定义。原稿把 Component.onCompleted 放在这个顶层矩形外面,在 Qt 6.10.2 会报“Component elements may not contain properties other than id”。应把处理器写在内部矩形上,它才会在每次创建矩形实例时运行。这个完成信号由 Component 类型提供附加机制,并不意味着处理器必须写在 Component {} 对象上。父子对象的完成处理器执行顺序不应作为业务依赖。
下面用 Loader 控制实例的生灭,并用合法的 State 和 onStateChanged 观察状态。保存为 LifecycleDemo.qml:
import QtQuick
Item { id: root width: 320 height: 180 property int rootsCompleted: 0 property int instancesCompleted: 0 property int instancesDestroyed: 0 property int stateChanges: 0 property bool activeMode: false
Component { id: factory Rectangle { width: 80 height: 80 color: "tomato" Component.onCompleted: root.instancesCompleted += 1 Component.onDestruction: root.instancesDestroyed += 1 } }
Loader { id: loader active: false sourceComponent: factory }
Rectangle { id: indicator x: 120 width: 80 height: 80 color: "gray" }
states: State { name: "active" when: root.activeMode PropertyChanges { target: indicator color: "green" } } onStateChanged: root.stateChanges += 1 Component.onCompleted: root.rootsCompleted += 1
function createOne() { loader.active = true } function releaseOne() { loader.active = false }}调用 createOne() 后,instancesCompleted 增加;释放后再次创建,会产生新的实例。Loader 与销毁调度的细节应按事件循环观察,不要在所有场景中假定销毁信号都在赋值语句中同步完成。销毁处理器适合有限的清理或观察,不能依赖其他对象此时仍然完整存活。
原稿的 State.onActiveChanged 与 if (active) 不是 State 的有效附加接口。这里改变 activeMode 后,when 决定根项目的 state 是否为 "active",根项目的普通 stateChanged 通知再触发 onStateChanged。State.when、Item.state 与附加生命周期信号应分别理解。
C++ 自定义附加属性:对象、工厂与注册
跳转到“C++ 自定义附加属性:对象、工厂与注册”自定义实现要区分三个角色:接收属性的 QML 对象、保存每个对象状态的附加对象,以及公开限定名称的提供者类型。提供者的静态工厂名是 qmlAttachedProperties(QObject *)。qmlAttachedPropertiesObject<T>() 则是 C++ 获取已缓存附加对象的辅助函数,不能把二者当成同一个待实现接口。
下面采用显式类型注册方式,保存四个文件到同一目录。attached.h:
#pragma once#include <QObject>#include <QtQml/qqml.h>
class AttachedValues : public QObject { Q_OBJECT Q_PROPERTY(int value READ value WRITE setValue NOTIFY valueChanged)public: explicit AttachedValues(QObject *parent = nullptr) : QObject(parent) {} int value() const { return value_; } void setValue(int value) { if (value_ == value) return; value_ = value; emit valueChanged(); }signals: void valueChanged();private: int value_ = 0;};
class MyAttachedType : public QObject { Q_OBJECTpublic: explicit MyAttachedType(QObject *parent = nullptr) : QObject(parent) {} static AttachedValues *qmlAttachedProperties(QObject *object) { return new AttachedValues(object); }};QML_DECLARE_TYPEINFO(MyAttachedType, QML_HAS_ATTACHED_PROPERTIES)main.cpp:
#include "attached.h"#include <QGuiApplication>#include <QQmlApplicationEngine>#include <QUrl>
int main(int argc, char **argv) { QGuiApplication app(argc, argv); if (app.arguments().size() != 2) return 2; qmlRegisterUncreatableType<MyAttachedType>( "Example.Attached", 1, 0, "MyAttachedType", "Use as attached properties"); QQmlApplicationEngine engine; engine.load(QUrl::fromLocalFile(app.arguments().at(1))); if (engine.rootObjects().isEmpty()) return 1; return app.exec();}Main.qml:
import QtQuickimport QtQuick.Controlsimport Example.Attached 1.0
Window { id: root width: 360 height: 180 visible: true property int firstChanges: 0
Item { id: first objectName: "first" MyAttachedType.value: 42 MyAttachedType.onValueChanged: root.firstChanges += 1 } Item { id: second objectName: "second" MyAttachedType.value: 7 }
Column { spacing: 12 Text { text: "first=" + first.MyAttachedType.value + ", second=" + second.MyAttachedType.value } Button { text: "增加 first" onClicked: first.MyAttachedType.value += 1 } }}CMakeLists.txt:
cmake_minimum_required(VERSION 3.20)project(AttachedExample LANGUAGES CXX)set(CMAKE_CXX_STANDARD 17)set(CMAKE_CXX_STANDARD_REQUIRED ON)set(CMAKE_AUTOMOC ON)find_package(Qt6 REQUIRED COMPONENTS Quick Qml)qt_add_executable(attached-example main.cpp attached.h)target_link_libraries(attached-example PRIVATE Qt6::Quick Qt6::Qml)使用 Qt Creator 选择安装好的 Qt 6 kit,配置、构建此项目;运行参数传入 Main.qml 的绝对路径,并确保运行环境能找到 Qt Quick Controls 模块。给 first 改值不会修改 second,因为两者各有自己的 AttachedValues。附加对象以接收对象为 QObject 父对象;引擎缓存这份关联,不应在接收对象仍存活时擅自删除附加对象。
采用自动模块注册的项目,可在提供者类上使用 QML_ATTACHED(AttachedValues)、QML_ELEMENT,再由 qt_add_qml_module 生成注册信息。那是另一套完整注册方式;不能只加一个宏就省略模块构建与注册。上面的显式示例通过 QML_DECLARE_TYPEINFO 声明附加能力,通过 qmlRegisterUncreatableType 注册限定名称。
依据:Qt 6.10 从 C++ 定义附加对象、Component、State。普通属性与别名见 QML 入门,独立信号连接见 Connections。