公司新闻MSC行业新闻Qt行业新闻行业新闻QtSquishCocoTest CenterAxivionQt系列产品MSC AdamsMSC NastranMSC MARCSimufact.WeldingCAE FatigueRomaxMSC系列产品SimericsProcastVA ONESYSWELD国外工业软件国产动力学分析软件(DAP)国产疲劳分析软件国产复合材料热压罐成型工艺优化软件试验数据快速处理分析系统软件国产结构(微纳尺度)分析软件国产工业软件超高周疲劳材料、构件测试产品检测可靠性测试设备特种光纤器具硬件产品(MCT)行业解决方案学科解决方案技术方案MSC系列产品培训Qt系列产品培训技术培训MSC系列资源下载中心Qt系列资源下载中心Simerics资源下载中心文档下载
我们致力于推动科技创新,以先进的技术和优质的服务为企业创造更大的价值
办公室电话:
028-80269112
欢迎您访问南通玛尔斯科仿真科技有限公司!

知识 | 将QML编译为C++: 解绑依赖关系

发表时间:2022-09-20 16:16

本文翻译自Compiling QML to C++: Untangling dependencies(原文发布于2022年6月13日)

原文作者:Qt公司高级软件工程师Ulf Hermann

校审:Josh Zheng

本文是关于如何优化QML应用程序以充分利用qmlsc的系列博文的第五篇。在第一篇中,我们搭建了运行环境。为了便于理解后续文章,建议您先阅读第一篇。在之前的几篇博文中,我们已经解决了 多个 其他问题,通过本篇文章,我们将学习如何理清QML文档之间的循环依赖关系。

我们再来重新回顾一下 CategoryLabel.qml,可以看到它的“draggerParent”属性存在上一篇文章中描述的duck类型问题。当尝试编译“contentBottom”上的绑定值时,问题就会出现:


Error: CategoryLabel.qml:44:47: Could not compile binding for contentBottom: Cannot load property contentY from QQuickItem of (component in .../src/libs/tracing/CategoryLabel.qml)::draggerParent with type QQuickItem.

幸运的是,这里的解决方案看起来更简单。我们可以在TimelineLabels.qml初始化draggerParent属性时看出,它属于TimelineLabels类型:

draggerParent: categories

现在我们虽然可以将属性指定为 TimelineLabels类型,但这样做会在运行时触发错误。TimelineLabels将CategoryLabel实例化, 也因而依赖CategoryLabel。由于QML引擎的限制,我们无法创建循环依赖关系。所以在CategoryLabel.qml中使用 TimelineLabels类型是行不通的。然而,我们需要的属性实际上又来自TimelineLabels的基本类型Flickable。在不创建循环依赖关系的情况下,我们可以这样写:


property Flickable draggerParent

但这并没有起到什么作用:


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以及它的非FINAL属性不在我们的控制范围内(尽管不是FINAL这件事本身就是QtQuick中的一个bug)。让我们回想一下,当我们发现循环依赖关系时,可能就已经开始怀疑这一点了。QML组件之间的循环依赖通常意味着组件耦合过紧。与其将 Flickable传递给每个CategoryLabel,并将每个CategoryLabel与Flickable的各项参数绑定,我们可能只需要让 CategoryLabel 获取它需要的任何信息来定位它自己。然后它也可以在一个具有不同父元素的上下文中实例化。

我们需要实现的是让 CategoryLabel 在父元素的当前可见区域中能够被拖动,同时将其定位在“内容”区域中。因此我们需要传递一个 contentY和一个visibleHeight。在此基础上,我们可以将draggerParent保留为Item,以便在选择父元素时保持灵活性,同时仍然允许 CategoryLabel能够拖动。我们也可以在这里添加一些默认值:

property Item draggerParent

property real contentY: 0

property real visibleHeight: contentHeight


实例化如下所示:







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

这一步有什么作用?让我们看一下在“contentBottom”上的绑定值,然后修改为:


property int contentBottom: contentY + visibleHeight - dragOffset

我们可以对Qt Creator加载示例跟踪文件进行分析,就像我们在之前的博文中所做的那样。这里的绑定赋值执行了82次。如果没有优化,整个绑定赋值耗时为153微秒,执行 JavaScript就要耗费108微秒。经过优化后,这两段时间分别缩短至66微秒和34微秒。

这一优化很大程度上是因为我们减少了该绑定值所需的查找次数。在这之前我们必须用 draggerParent.contentY和 draggerParent.height,而非contentY和visibleHeight。查找draggerParent.contentY首先要经过在一定范围内查找draggerParent,然后在结果对象中再查找 contentY。由于解释器无法证明此操作不会改变draggerParent本身,因此在获取draggerParent.height时会对draggerParent进行重复查找。因此,以前要执行五次查找操作,经过优化后只需要三次。但是,由于我们初始化CategoryLabel的方式有所不同,其中的一些操作是发生在其他地方的。因此,我们需要进行更详尽的分析才能确定累积优化时间的确切数字。

您可能会尝试整个“contentBottom”的绑定值内联到它被调用的地方,但这通常并不是一个好主意。如果我们这样做,当类别扩展时,类别下的每个子标签都会进行重复计算。如前文所述,检索值所需的查找是有开销的。查找一个值(contentBottom)比查找三个值(contentY、visibleHeight、dragOffset)的开销要小得多。

兼容性

QML从早期版本开始就已经支持这类优化。这种解耦组件的方式是个不错的选择。而Qt 6可以为您在将QML编译为C++方面提供额外的便利。


分享到:
全国统一服务热线:

028-80269112

咨询邮箱xianghui.li@bjmars.cn



关注微信公众号
了解最新动态
下载中心