Qt 可绑定属性

Qt 提供可绑定属性。可绑定属性是具有值或使用任何 C++ 函数(通常是 C++ lambda 表达式)指定的属性。如果使用 C++ 函数指定了这些属性,只要它们的依赖关系发生变化,它们就会自动更新。

可绑定属性在类QProperty 和类QObjectBindableProperty 中实现,前者由数据对象和指向管理数据结构的指针组成,后者仅由数据对象组成,并使用封装QObject 来存储指向管理数据结构的指针。

为什么使用可绑定属性?

属性绑定是 QML 的核心功能之一。它们允许指定不同对象属性之间的关系,并在依赖关系发生变化时自动更新属性值。可绑定属性不仅可以在 QML 代码中实现,也可以在 C++ 中实现。使用可绑定属性有助于简化程序,因为它省去了大量用于跟踪和响应不同对象依赖关系更新的模板代码。

下面的介绍性示例演示了如何在 C++ 代码中使用可绑定属性。您也可以查看可绑定属性示例,了解可绑定属性如何帮助改进您的代码。

介绍性示例

绑定表达式通过读取其他QProperty 值来计算值。在幕后,这种依赖关系会被跟踪。只要检测到任何属性的依赖关系发生变化,就会重新评估绑定表达式,并将新的结果应用到该属性。例如

QProperty<QString>firstname("John");QProperty<QString> 姓("史密斯");QProperty<int>age(41);QProperty<QString> 全名;fullname.setBinding([&]() {returnfirstname.value()+ " " +lastname.value()+ " age: " +QString::number(age.value()); });
qDebug() << fullname.value(); // Prints "John Smith age: 41"

firstname= "Emma";// 触发绑定重新评估
qDebug() << fullname.value(); // Prints the new value "Emma Smith age: 41"

// 生日快到了age.setValue(age.value()+ 1);// 触发重新评估
qDebug() << fullname.value(); // Prints "Emma Smith age: 42"

firstname 属性分配了一个新值时,将重新评估fullname 的绑定表达式。因此,当最后一条qDebug() 语句试图读取fullname 属性的名称值时,就会返回新值。

由于绑定是 C++ 函数,它们可以做任何 C++ 中可能做的事情。这包括调用其他函数。如果这些函数访问QProperty 持有的值,它们就会自动成为绑定的依赖项。

绑定表达式可以使用任何类型的属性,因此在上面的示例中,年龄是一个整数,并通过转换为整数折叠到字符串值中,但依赖关系是完全可跟踪的。

可绑定属性的获取器和设置器

当一个类有一个可绑定的属性(使用QPropertyQObjectBindableProperty )时,在为该属性设计获取器和设置器时必须特别小心。

可绑定属性获取器

为确保自动依赖跟踪系统的正常运行,获取器中的每一条可能的代码路径都需要从底层属性对象中读取。此外,不得在获取器中写入属性。在获取器中重新计算或刷新任何内容的设计模式与可绑定属性不兼容。

因此,建议只在可绑定属性中使用琐碎的获取器。

可绑定属性设置器

为确保自动依赖跟踪系统的正常运行,设置器中的每一个可能的代码路径都需要写入底层属性对象,即使值没有改变。

设置器中的任何其他代码都很可能不正确。任何根据新值进行更新的代码都很可能是一个错误,因为当通过绑定更改属性时,这些代码不会被执行。

因此,建议只在可绑定属性中使用琐碎的设置器。

写入可绑定属性

可绑定属性会将每次更改通知其从属属性。这可能会触发变化处理程序,反过来又可能调用任意代码。因此,必须仔细检查对可绑定属性的每次写入。可能会出现以下问题。

向可绑定属性写入中间值

可绑定属性不得用作算法中的变量。写入的每个值都会传递给从属属性。例如,在下面的代码中,依赖于myProperty的其他属性将首先被告知42 的变化,然后被告知maxValue 的变化。

myProperty = somecomputation(); // returning, say, 42
if (myProperty.value() > maxValue)
    myProperty = maxValue;

取而代之的是,在一个单独的变量中执行计算。正确的用法见下面的示例。

int newValue = someComputation();
if (newValue > maxValue)
    newValue = maxValue;
myProperty = newValue; // only write to the property once

在过渡状态下编写可绑定属性

当可绑定属性是一个类的成员时,对该属性的每次写入都可能向外部暴露当前状态。因此,当类的不变式未满足时,不可在瞬态中编写可绑定属性。

例如,在一个表示圆的类中,它的两个成员radiusarea是一致的,setter 可能是这样的(其中 radius 是一个可绑定属性):

void setRadius(double newValue)
{
    radius = newValue; // this might trigger change handlers
    area = M_PI * radius * radius;
    emit radiusChanged();
}

在这里,在变化处理程序中触发的代码可能会使用该圆,虽然它有新的半径,但仍然是旧的面积。

使用虚拟设置器和获取器的可绑定属性

通常情况下,属性设置器和获取器应该是最小的,除了设置属性外什么也不做;因此,通常不适合使用虚拟设置器和获取器。派生类不应该做任何事情。

不过,有些 Qt 类的属性可以使用虚拟设置器。在子类化此类 Qt 类时,覆盖此类设置器需要特别小心。

无论如何,必须调用基本实现才能正确绑定。

下面将说明这种方法。

void DerivedClass::setValue(int val)
{
    // do something
    BaseClass::setValue(val);
    // probably do something else
}

有关写入可绑定属性的所有常见规则和建议在此同样适用。一旦基类实现被调用,所有观察者都会收到有关属性变更的通知。这意味着在调用基类实现之前,必须满足类的不变式。

在有必要使用虚拟获取器或设置器的极少数情况下,基类应记录其对重载的要求。

制定属性绑定

任何评估为正确类型的 C++ 表达式都可以用作绑定表达式,并提供给 setBinding() 方法。不过,要编写正确的绑定表达式,必须遵循一些规则。

依赖关系跟踪只适用于可绑定的属性。开发人员有责任确保绑定表达式中使用的所有属性都是可绑定属性。当绑定表达式中使用了非绑定属性时,对这些属性的更改不会触发对绑定属性的更新。编译时或运行时都不会生成警告或错误。只有当绑定表达式中使用的可绑定属性发生变化时,才会更新绑定属性。如果可以确保在每次改变非绑定依赖关系时都调用被绑定属性的 markDirty,则绑定表达式中可以使用非绑定属性。

绑定的属性可能会在其生命周期内多次评估其绑定。开发人员必须确保绑定表达式中使用的所有对象的生命周期都长于绑定的生命周期。

可绑定属性系统不是线程安全的。一个线程上绑定表达式中使用的属性不得被任何其他线程读取或修改。QObject 派生类的对象如果具有绑定属性,则不得移动到其他线程。同样,QObject 派生类的对象,如果其属性在绑定中使用,也不得移动到不同的线程中。在这种情况下,是在同一对象中的一个属性的绑定中使用,还是在另一个对象中的一个属性的绑定中使用,并不重要。

绑定表达式不应读取其绑定的属性。否则,会出现一个评估循环。

绑定表达式不得写入绑定的属性。

用作绑定的函数以及在绑定中调用的所有代码都不得使用 co_await。否则会混淆属性系统对依赖关系的跟踪。

可绑定属性和多线程

除非另有说明,否则可绑定属性不是线程安全的。除创建时所在的线程外,其他线程不得读取或修改可绑定属性。

跟踪可绑定属性

有时,属性之间的关系无法通过绑定来表达。相反,你可能需要在属性值发生变化时运行自定义代码,而不是将属性值赋值给另一个属性,而是将其传递给应用程序的其他部分。例如,将数据写入网络套接字或打印调试输出。QProperty 提供了两种跟踪机制。

您可以使用 onValueChanged() 注册一个回调函数,以便在属性值发生变化时调用。如果您希望在属性的当前值发生变化时也调用回调函数,请使用 subscribe() 注册回调函数。

与 Q_PROPERTYs 交互

定义了BINDABLE 的 Q_PROPERTY可在绑定表达式中绑定和使用。您可以使用QPropertyQObjectBindablePropertyQObjectComputedProperty 来实现此类属性。

不包含BINDABLE 的 Q_PROPERTY 只要定义了NOTIFY 信号,也可以绑定并在绑定表达式中使用。您必须使用QBindable(QObject* obj, const char* property) 构造函数将该属性封装在QBindable 中。然后,可以使用QBindable::setBinding() 绑定该属性,或通过QBindable::value() 在绑定表达式中使用该属性。如果属性不是BINDABLE ,则必须在绑定表达式中使用QBindable::value() 代替正常的属性READ 函数(或MEMBER ),以启用依赖关系跟踪。

© 2025 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.