Qt 可绑定属性
Qt 提供了可绑定属性。可绑定属性是指具有特定值,或通过任意 C++ 函数(通常是 C++ lambda 表达式)来指定的属性。如果通过 C++ 函数指定,则每当其依赖项发生变化时,这些属性会自动更新。
可绑定属性在类QProperty 中实现,该类由数据对象和指向管理数据结构的指针组成;同时也在类QObjectBindableProperty 中实现,该类仅包含数据对象,并使用封装类QObject 来存储指向管理数据结构的指针。
为何要使用可绑定属性?
属性绑定是 QML 的核心功能之一。它允许指定不同对象属性之间的关系,并在依赖关系发生变化时自动更新属性的值。 可绑定属性不仅能在 QML 代码中实现这一功能,在 C++ 中同样适用。使用可绑定属性有助于简化程序,因为它消除了大量用于跟踪和响应不同对象依赖关系更新的冗余代码。
下面的入门示例演示了在 C++ 代码中使用可绑定属性的方法。您还可以查看“可绑定属性”示例,了解可绑定属性如何帮助改进您的代码。
入门示例
绑定表达式通过读取其他QProperty 的值来计算结果。系统会在后台跟踪这种依赖关系。每当检测到任何属性的依赖关系发生变化时,系统都会重新评估绑定表达式,并将新结果应用到该属性上。例如:
QProperty<QString>firstname("John");
QProperty<QString>lastname("Smith");
QProperty<int>age(41);
QProperty<QString>fullname;
fullname.setBinding([&]() {returnfirstname.value()+ " " +lastname.value()+ " 年龄: " +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 属性的name值时,将返回新值。
由于绑定是 C++ 函数,它们可以执行 C++ 中所有可能的操作,包括调用其他函数。如果这些函数访问了QProperty 持有的值,它们会自动成为该绑定的依赖项。
绑定表达式可以使用任何类型的属性,因此在上例中,age 是一个整数,并通过转换为整数的方式折叠为字符串值,但该依赖关系会被完全追踪。
可绑定属性的获取器和设置器
当类拥有可绑定属性(无论是通过 `QProperty ` 还是 `QObjectBindableProperty` 实现)时,在为该属性编写获取器和设置器时必须格外谨慎。
可绑定属性的获取器
为确保自动依赖追踪系统正常运行,获取器中的每个可能代码路径都必须从底层属性对象中读取数据。此外,获取器内部不得对该属性进行写入操作。在获取器中重新计算或刷新任何内容的設計模式与可绑定属性不兼容。
因此,建议对可绑定属性仅使用简单的获取器。
可绑定属性的设置器
为了确保自动依赖跟踪系统正常运行,设置器中的每个可能代码路径都需要向底层属性对象写入数据,即使该值未发生变化。
设置器中的任何其他代码极有可能存在错误。任何基于新值进行更新的代码很可能是一个错误,因为当属性通过绑定被更改时,该代码将不会被执行。
因此,建议对可绑定属性仅使用简单的设置器。
向可绑定属性写入数据
可绑定属性会将其每次更改通知给依赖属性。这可能会触发变更处理程序,进而可能调用任意代码。因此,对可绑定属性的每次写入都必须仔细检查。可能会出现以下问题。
将中间值写入可绑定属性
绝不能在算法中将可绑定属性用作变量。写入的每个值都会被传递给依赖属性。例如,在下面的代码中,依赖于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在过渡状态下写入可绑定属性
当可绑定属性是类的成员时,对该属性的每次写入都可能向外部暴露当前状态。因此,在类不变式未满足的瞬态状态下,绝不能对可绑定属性进行写入。
例如,在一个表示圆的类中,该类保持两个成员radius和area的一致性,其 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_PROPERTY 的交互
定义了BINDABLE 的Q_PROPERTY可以被绑定并在绑定表达式中使用。您可以使用QProperty 、QObjectBindableProperty 或QObjectComputedProperty 来实现此类属性。
即使未定义BINDABLE ,只要定义了NOTIFY 信号,Q_PROPERTY也可以被绑定并在绑定表达式中使用。 您必须使用QBindable(QObject* obj, const char* property) 构造函数将该属性封装在QBindable 中。然后,可以通过QBindable::setBinding() 对该属性进行绑定,或通过QBindable::value() 在绑定表达式中使用它。如果该属性不是BINDABLE ,您必须在绑定表达式中使用QBindable::value() 代替常规的属性READ 函数(或MEMBER ),以启用依赖关系跟踪。
© 2026 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.