QML 和 Qt Quick 的最佳实践

尽管 QML 和 Qt Quick 有很多优势,但在某些情况下,可能也会遇到问题。下面详细阐述了一些最佳做法,这些做法将帮助你在开发应用程序时获得更好的结果。

自定义UI控件

在当今世界,一个流畅和现代的UI是任何应用程序成功的关键,而这正是 QML 对设计师或开发者的意义所在。Qt 提供了最基本的UI控件,这些控件是创建一个流畅和现代的UI所必需的。建议在创建你自己的自定义UI控件之前,先浏览一下这个UI控件的列表。

除了Qt Quick本身提供的这些基本UI控件外,Qt Quick Controls还提供了一套丰富的UI控件。它们在不做任何改变的情况下满足了最常见的使用情况,并通过其定制选项提供了更多的可能性。特别是,Qt Quick Controls提供了符合最新UI设计趋势的样式选项。如果这些UI控件不能满足你的应用程序的需求,才建议你创建一个自定义控件。

编码规范

See QML 编码规范.

绑定应用程序资源

大多数应用程序都依赖诸如图像和图标等资源来提供丰富的用户体验。不管什么操作系统,要让应用程序可以使用这些资源往往会遇到问题。大多数流行的操作系统采用了更严格的安全策略,限制了对文件系统的访问,使得加载这些资源变得更加困难。作为一个替代方案,Qt 提供了自己的资源系统,并将其内置于应用程序的二进制文件中,使人们不管目标操作系统如何,都能够访问应用程序的资源。

例如,考虑以下项目目录结构。

 MyModule
 ├── images
 │   ├── image1.png
 │   └── image2.png
 ├── CMakeLists.txt
 └── main.qml

您可以通过以下方式将此结构表示为 CMake QML 模块

 qt_add_qml_module(my_module
    URI MyModule
    VERSION 1.0
    QML_FILES
        main.qml
    RESOURCES
        images/image1.png
        images/image2.png
    # ...
 )

所有列在 QML_FILES 下的QML文件将自动 提前 被编译。

将UI与逻辑分离

大多数应用程序开发人员想要实现的关键目标之一是创建一个可维护的应用程序。实现这一目标的方法之一是将UI与业务逻辑分开。以下是一个应用程序的UI应该用 QML 编写的几个原因:

  • 声明性语言非常适合定义UI
  • QML 代码更容易编写,因为它比 C++ 更简洁,并且不是强类型的。所以它是一种优秀的原型语言,在与设计师合作时,这种品质至关重要。
  • JavaScript可以很容易地在 QML 中使用,以响应事件。

作为一种强类型的语言,C++最适合用于应用程序的逻辑。通常,代码执行复杂的计算或数据处理等任务,在 C++ 中比 QML 更快。

Qt 提供了多种方法来将 QML 和 C++ 代码集成到应用程序中。一个典型的用例是在用户界面中显示数据列表。如果数据集是静态的、简单的、小型的,则用 QML 编写的模型就足够了。

以下片段演示了用 QML 编写的模型示例:

 model: [ "Item 1", "Item 2", "Item 3" ]

 model: 10

对于大型或频繁修改的动态数据集,使用 C++

公开 C++ 数据给 QML

重构 QML 比重构 C++ 容易得多,所以为了使维护工作不受影响,我们应该尽可能地使 C++ 类型不被 QML 知道。这可以通过将 C++ 类型的引用 “推送” 到 QML 中来实现。

这可以通过使用 required properties 并通过 QQmlApplicationEngine::setInitialProperties 设置来完成。也可以创建一个或多个 单例,它将返回 C++ 端想要提供给 QML 的所有数据。

使用这种方法,如果将来需要重构 QML,C++ 将保持不变。

有关选择正确方法将 C++ 类型公开给 QML 的快速指南,请参阅 选择正确的方法集成 C++ 和 QML

使用 Qt Quick Layouts

Qt 提供了 Qt Quick Layouts,用于在一个 layout 中直观地排列 Qt Quick items。与它的替代品 item positioners 不同,Qt Quick Layouts 还可以在窗口调整时调整其子项的大小。尽管 Qt Quick Layouts 通常是大多数使用情况下的理想选择,但在使用它们时必须注意以下事项:

应该

  • 使用 anchors宽度高度 属性来指定布局。
  • 使用 Layout 附加属性来设置布局的直接子级的大小和对齐属性

不应该

  • 不要为提供隐式宽度和隐式高度的 item 定义首选尺寸,除非它们的隐式尺寸不令人满意。
  • 不要在一个 layout 的直系子项上使用 anchors。请使用 Layout.preferredWidthLayout.preferredHeight
     RowLayout {
         id: layout
         anchors.fill: parent
         spacing: 6
         Rectangle {
             color: 'azure'
             Layout.fillWidth: true
             Layout.minimumWidth: 50
             Layout.preferredWidth: 100
             Layout.maximumWidth: 300
             Layout.minimumHeight: 150
             Text {
                 anchors.centerIn: parent
                 text: parent.width + 'x' + parent.height
             }
         }
         Rectangle {
             color: 'plum'
             Layout.fillWidth: true
             Layout.minimumWidth: 100
             Layout.preferredWidth: 200
             Layout.preferredHeight: 100
             Text {
                 anchors.centerIn: parent
                 text: parent.width + 'x' + parent.height
             }
         }
     }
    

注意:Layouts 和 anchors 都是需要更多内存和实例化时间的对象类型。当简单地绑定x、y、宽度和高度属性就足够了时,要避免使用它们(尤其是在列表和表格委托以及控件样式中)。

类型安全

在 QML 中声明属性时,使用 “var” 类型既简单又方便:

 property var name
 property var size
 property var optionsMenu

然而,这种方法有几个缺点:

  • 如果分配了一个错误类型的值,报告的错误将指向属性声明的位置,而不是属性被分配到的位置。这使得追踪错误更加困难,从而减慢了开发过程。
  • 无法进行静态分析来捕获上述错误。
  • 属性的实际底层类型无法让读者马上明白。

因此,建议使用实际的类型:

 property string name
 property int size
 property MyMenu optionsMenu

性能

有关 QML 和 Qt Quick 中性能的信息,请参阅 性能注意事项和建议

优先使用声明式绑定而不是命令式赋值

在 QML 中,可以使用命令式 JavaScript 代码来执行诸如响应输入事件、通过网络发送数据等任务。命令式代码在 QML 中占有重要地位,但也要注意何时不使用它。

例如,考虑下面这个命令式的赋值:

 Rectangle {
     Component.onCompleted: color = "red"
 }

它有以下的缺点:

  • 它很慢。color 属性将首先使用默认构造的值,然后再使用 “red” 。
  • 它把可能在构建时发现的错误推迟到运行时,从而减慢了开发过程。
  • 它会覆盖任何已存在的声明性绑定。在大多数情况下,这是有意的,但有时可能是无意的。有关更多信息,请参阅 调试绑定覆盖
  • 它干扰了工具;例如,Qt Quick Designer 就不支持 JavaScript。

可以将代码改写为声明性绑定:

 Rectangle {
     color: "red"
 }

工具和实用程序

有关使使用 QML 和 Qt Quick 更容易的有用工具和实用程序的信息,请参阅 Qt Quick 工具和实用程序.

Scene Graph

有关 Qt Quick Scene Graph 的信息,请参阅 Qt Quick Scene Graph.

可缩放的UI

随着显示分辨率的提高,可缩放的应用程序 UI 变得越来越重要。实现此目标的方法之一是为不同的屏幕分辨率维护多个 UI 副本,并根据可用的分辨率加载适当的副本。虽然这种方法效果很好,但它增加了维护的开销。

Qt 为这个问题提供了更好的解决方案,并建议应用程序开发人员遵循:

  • 使用 anchors 或 Qt Quick Layouts 模块来布置可视 item。
  • 不要为可视 item 指定明确的宽度和高度。
  • 为您的应用程序支持的每种显示分辨率提供 UI 资源,例如图像和图标。Qt Quick Controls Gallery 示例很好地证明了这一点,它为 @2x@3x@4x 分辨率提供了 qt-logo.png,使应用程序能够满足高分辨率显示器的需求。只要明确启用了高 DPI 缩放功能,Qt 就会自动选择合适的图像给显示器。
  • 为小图标使用 SVG 图像。虽然较大的 SVG 渲染速度可能很慢,但较小的 SVG 效果很好。位图图像需要提供一个图像的多个版本,而矢量图像却不用。
  • 使用基于字体的图标,如 Font Awesome。这些图标可以在任意显示分辨率缩放,并且还允许着色。Qt Quick Controls 文本编辑器示例很好地证明了这一点。

有了这些,应用程序的 UI 就可以根据不同的分辨率来缩放。