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([&]() { return firstname.value() + " " + lastname.value() + " age: " + QString::number(age.value()); });

qDebug() << fullname.value(); // Prints "John Smith age: 41"

firstname = "Emma"; // Triggers binding reevaluation

qDebug() << fullname.value(); // Prints the new value "Emma Smith age: 41"

// Birthday is coming up
age.setValue(age.value() + 1); // Triggers re-evaluation

qDebug() << fullname.value(); // Prints "Emma Smith age: 42"

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

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

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

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

当类有可绑定属性时,无论是使用QProperty还是QObjectBindableProperty,在为该属性制定获取器和设置器时必须格外小心。

可绑定属性获取器

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

因此,建议只使用用于可绑定属性的简单getter。

可绑定属性设置器

为确保自动依赖跟踪系统的正常运行,setter中的所有可能路径都需要向底层属性对象写入,即使值没有发生变化。

setter中的任何其他代码很可能是不正确的。任何基于新值进行更新的代码很可能是bug,因为这些代码在属性通过绑定进行更改时不会执行。

因此,建议只使用用于可绑定属性的简单setter。

写入可绑定属性

可绑定属性会通知其依赖属性每次更改。这可能会触发更改处理器,进而可能调用任意代码。因此,每个可绑定属性的写入都需要仔细检查。可能会出现以下问题。

将中间值写入可绑定属性

可绑定属性不能用作算法中的变量。写入的每个值都会传递给依赖属性。例如,在以下代码中,依赖于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();
}

在这里,由更改处理器触发的代码可能使用圆形,而此时它已经有了新的半径,但仍然保留了旧的面积。

具有虚拟设置器和获取器的可绑定属性

属性设置器和获取器通常应该是最小的,只是设置属性;因此,通常不适当 such setters 和 getters 是虚拟的。对于派生类来说,没有什么是有意义的。

然而,某些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的交互

定义为Q_PROPERTY可以绑定并用于绑定表达式。您可以使用QPropertyQObjectBindablePropertyQObjectComputedProperty实现此类属性。

没有 BINDABLE 的 Q_PROPERTY 同样可以根据定义的 NOTIFY 信号进行绑定和使用,在绑定表达式中使用。您必须使用 QBindable(QObject* obj, const char* property) 构造函数将属性包装在一个 QBindable 中。然后,可以通过 QBindable::setBinding() 绑定该属性,或者通过 QBindable::value() 在绑定表达式中使用。如果您想要在不将属性标记为 BINDABLE 的情况下启用依赖跟踪,请在绑定表达式中使用 QBindable::value() 而不是通常的属性 READ 函数(或 MEMBER)。

© 2024 The Qt Company Ltd. 包含在此处的文档贡献是各自所有者的版权。此处提供的文档是根据 Free Software Foundation 发布的 GNU 自由文档许可证版本 1.3 的条款许可的。Qt 和相关商标是 The Qt Company Ltd. 在芬兰以及/或全球其他国家的商标。所有其他商标均为各自所有者的财产。