将QML编译为C++:解开依赖关系

Compiling QML to C++: Untangling dependencies

将QML编译为C++:解开依赖关系

Monday June 13, 2022 by Ulf Hermann | Comments

​2022年6月13日,星期一,乌尔夫·赫尔曼评论

This is the fifth installment in the series of blog posts on how to adjust your QML application to take the maximum advantage of qmlsc. In the first post we've set up the environment. You should read that post first in order to understand the others. After fixing various other problems in the previous posts we're going to learn how to straighten out cyclic dependencies between QML documents.

​这是关于如何调整QML应用程序以最大限度地利用qmlsc的博客文章系列的第五部分。在第一篇文章中,我们设置了环境。为了理解,你应该先读那篇文章。在解决了前面文章中的各种其他问题之后,我们将学习如何理顺QML文档之间的循环依赖关系。

Looking at CategoryLabel.qml again, we can see that its "draggerParent" property has the duck typing problem described in the previous post. The problem surfaces when trying to compile the binding on "contentBottom":

再次​查看CategoryLabel.qml,我们可以看到它的“draggerParent”属性存在前一篇文章中描述的duck类型问题。在尝试编译“contentBottom”上的绑定时,问题就会出现:

Luckily, here the solution looks simpler. "draggerParent" is a TimelineLabels as we can see from the one place where it's initialized.

幸运的是,这里的解决方案看起来更简单。“draggerParent”是一个TimelineLabels,我们可以从它初始化的地方看到。

Now we could just type the property as TimelineLabels, but that would trigger an error at run time. TimelineLabels instantiates and therefore depends on CategoryLabel. Due to limitations in the QML engine we cannot create cyclic dependencies. Using the TimelineLabels type in CategoryLabel.qml will therefore not work. However, the properties we need are actually from TimelineLabels' base type, Flickable. We can easily state that without creating a cyclic dependency:

现在我们可以将属性键入TimeLineLabel,但这会在运行时触发错误。TimeLineLabel实例化,因此依赖于CategoryLabel。由于QML引擎中的限制,我们无法创建循环依赖项。使用CategoryLabel中的TimelineLabels类型。因此,qml将不起作用。然而,我们需要的属性实际上来自TimelineLabels的基类型Flickable。我们可以很容易地指出,无需创建循环依赖关系:

property Flickable draggerParent

Unfortunately, this doesn't help:

不幸的是,这无助于:

Error: CategoryLabel.qml:44:47: Could not compile binding for contentBottom: Member contentY of QQuickFlickable of (component in .../src/libs/tracing/CategoryLabel.qml)::draggerParent with type QQuickFlickable can be shadowed
    property int contentBottom: draggerParent.contentY + draggerParent.height - dragOffset

QQuickFlickable and it's non-FINAL properties are outside of our control (although the fact that this one isn't FINAL is a bug in QtQuick). Let's take a step back here. When we identified the cyclic dependency, we might have gotten suspicious already. Cyclic dependencies between QML components usually tell us that the components are coupled too tightly. Rather than passing the Flickable into each CategoryLabel, and therefore tying each CategoryLabel to the specifics of a Flickable, we might just tell the CategoryLabel whatever it needs to know to position itself. Then it can also be instantiated in a context where we have a different parent element.

QQuickFlickable及其非FINAL属性超出了我们的控制范围(尽管这个不是FINAL的事实是QtQuick中的一个bug)。让我们后退一步。当我们确定循环依赖关系时,我们可能已经开始怀疑了。QML组件之间的循环依赖通常告诉我们组件之间的耦合太紧。与其将Flickable传递到每个CategoryLabel中,并因此将每个CategoryLabel与Flickable的细节联系起来,不如告诉CategoryLabel定位自身所需的任何信息。然后,也可以在具有不同父元素的上下文中实例化它。

What we need to achieve is to allow the CategoryLabel to be dragged around in the currently visible area of the parent element, while being positioned in its "content" area. Therefore we need to pass a contentY and a visibleHeight. With this in place, we can keep the draggerParent as Item in order to remain flexible in the choice of parent elements, while still allowing CategoryLabel to implement its dragging behavior. Adding a few defaults doesn't hurt here:

我们需要实现的是允许CategoryLabel在父元素的当前可见区域中拖动,同时定位在其“内容”区域中。因此,我们需要通过一个满足感和明显的高度。有了这一点,我们可以将draggerParent保留为项,以便在选择父元素时保持灵活性,同时仍然允许CategoryLabel实现其拖动行为。在这里添加一些默认值不会有什么坏处:

property Item draggerParent
property real contentY: 0
property real visibleHeight: contentHeight

The instantiation then looks as follows:

然后,实例化如下所示:

CategoryLabel {
    id: label
    // [...]
    draggerParent: categories
    contentY: categories.contentY
    visibleHeight: categories.height
    // [...]
}

What does it do? Let's look at the binding on "contentBottom", which is then changed to:

它有什么作用?让我们看一下“contentBottom”上的绑定,然后将其更改为:

property int contentBottom: contentY + visibleHeight - dragOffset

We can profile Qt creator loading an example trace file again, like we did in the previous posts. The binding is executed 82 times here. Without the optimization, I get 153µs for he whole binding and 108µs for only the JavaScript execution. With the optimization, it's 66µs and 34µs.

我们可以像在前面的帖子中一样,再次分析Qt-creator加载示例跟踪文件的情况。这里执行了82次绑定。在没有优化的情况下,整个绑定得到153µs,仅JavaScript执行得到108µs。经过优化后,它分别为66µs和34µs。

This speedup is in large parts due to the fact that we've reduced the number of lookups needed in this particular binding. Previously we had to get draggerParent.contentY and draggerParent.height instead of contentY and visibleHeight. A lookup of draggerParent.contentY results in first a lookup of draggerParent in the scope, and then a lookup of contentY in the resulting object. As the interpreter cannot prove that this operation doesn't change draggerParent itself, it will repeat the lookup for draggerParent when getting draggerParent.height. Therefore, previously we had five lookup operations, and now we have three. However, due to the way we initialize CategoryLabel, we have shifted some of this effort to a different place. A more detailed analysis would be necessary to put an exact number on the cumulative effect.

这种加速在很大程度上是由于我们减少了这种特定绑定所需的查找数量。之前我们必须得到draggerParent.contentY和draggerParent.height,而不是满足感和可见高度。draggerParent.contentY的查找,首先在范围中查找draggerParent,然后在结果对象中查找contentY。由于解释器无法证明此操作不会更改draggerParent本身,因此在获取draggerParent时,它将重复对draggerParent.height的查找。因此,以前我们有五个查找操作,现在有三个。然而,由于我们初始化CategoryLabel的方式,我们将一些工作转移到了另一个地方。需要进行更详细的分析,以确定累积效应的确切数字。

You may be tempted to inline the whole "contentBottom" binding into the one place where it's used. However, this is not generally a good idea. If we do so, the calculation will be repeated for each of the sub-labels when the category is expanded. As we have seen, the lookups needed to retrieve the values are not free. Looking up only one value (contentBottom) is substantially cheaper than looking up three of them (contentY, visibleHeight, dragOffset).

您可能想将整个“contentBottom”绑定内联到使用它的一个地方。然而,这通常不是一个好主意。如果这样做,当类别展开时,将对每个子标签重复计算。正如我们所看到的,检索值所需的查找不是免费的。只查找一个值(contentBottom)要比查找其中三个值(contentY、visibleHeight、dragOffset)便宜得多。

Compatibility

兼容性

All of this has been possible since the earliest days of QML. It's a good idea to decouple your components this way. The compilation to C++ is an added benefit you can then get when switching to Qt 6.

自QML的早期以来,所有这些都是可能的。以这种方式解耦组件是一个好主意。编译到C++是切换到Qt 6时可以获得的额外好处。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值