开发停车应用程序

提供逐步说明,说明如何为 Neptune 3 UI 开发停车应用程序。

简介

本教程向您展示如何通过显示指定区域内可用停车场的数量来逐步构建停车应用程序,并使用一些静态数据。

本教程分为几个章节

  1. 设计和实现基本应用程序
  2. 扩展应用程序并与 Qt 应用程序管理器的 Intent 和通知功能集成
  3. 使用中间件 API 扩展应用程序并提供一些仿真

第1章:设计和实现应用程序

我们从 Main.qml 文件开始,其中我们导入所需的模块。除了 Qt Quick 之外,我们还需要一些强制性导入

  • application.windows - 对于 ApplicationCCWindow 是必需的
  • shared.Sizes - 是一个附加属性,其中包含一些用于 Neptune 3 UI 的大小值
  • shared.animations - 对于 Neptune 3 UI 中的某些动画是必需的
import application.windows 1.0
import shared.Sizes 1.0
import shared.Style 1.0
import shared.controls 1.0

ApplicationCCWindow {
    id: root

    property bool parkingStarted: false

    Item {
        x: root.exposedRect.x
        y: root.exposedRect.y
        width: root.exposedRect.width
        height: root.exposedRect.height

我们使用 ApplicationCCWindow 作为停车应用程序的根元素,因为应用程序在中央控制台上显示。在 ApplicationCCWindow 之上有一个项目,用于承载内容。当应用程序启动时,我们使用一些专用 API 在中央控制台上保留一个矩形区域。

exposedRect 属性保存暴露给用户的窗口区域。这是不占用其他 UI 元素的区域。

一旦应用程序已保留此区域,让我们开始工作 UI。

设置 UI

您可以从现有附加的属性中使用一些属性,以及 Neptune 3 UI 动画。其中之一是

属性描述
SizesSizes.dp() 是一个函数,用于在 Neptune 3 UI 的窗口正在调整大小时保留 UI 像素密度。此函数将像素值从参考像素密度转换为当前密度。此外,Sizes.dp() 会对给定的像素值应用当前缩放因子,有效地将其转换为设备像素(dp)。有时,此函数还可以将像素向上舍入到最接近的整数,以最大限度地减少锯齿伪影。

注意:一些字体大小,如fontSizeXXSfontSizeXSfontSizeSfontSizeMfontSizeLfontSizeXXL,基于Neptune 3 UI的设计具有预定义值。

样式样式是一个附加属性,提供了与UI样式相关的值,如当前选定的主题、颜色和不透明度级别。
动画有几个默认动画和平滑动画,当您需要在UI上应用此行为时可以使用它们。它们包含预定义的值,以保持Neptune 3 UI中任何移动对象的动画一致性。

通常,Neptune 3 UI应用程序分为两部分:上部内容和下部内容。这是我们用于音乐应用和日历应用的设计理念。现在,对于停车应用

  • 上部内容是用于停车票
  • 下部内容用于显示详细信息
填写上部内容

由于上部内容有背景,我们使用Image作为它的根元素并设置背景源。为了返回正确的图像源,我们使用Style.image("app-fullscreen-top-bg", Style.theme)Style.image()是一个函数,需要图像文件名和当前选中主题。在Neptune 3 UI中,我们支持两种主题:暗色(默认)和亮色。对于我们使用的每个资产,我们必须提供两个文件;每个主题一个。

        Image {
            id: topContent
            width: parent.width
            height: Sizes.dp(500)
            source: Style.image("app-fullscreen-top-bg", Style.theme)

            Label {
                text: qsTr("No active parking tickets")
                anchors.centerIn: parent
                font.weight: Font.Light
                opacity: !root.parkingStarted ? 1.0 : 0.0
                Behavior on opacity { DefaultNumberAnimation {} }
            }

            Image {
                width: root.width * 0.8
                height: topContent.height
                source: "assets/ticket_bg.png"
                anchors.top: parent.top
                anchors.right: parent.right

                anchors.rightMargin: root.parkingStarted ? 0 : - width * 0.85
                Behavior on anchors.rightMargin { DefaultNumberAnimation {} }

                Column {
                    anchors.left: parent.left
                    anchors.leftMargin: Sizes.dp(130)
                    anchors.verticalCenter: parent.verticalCenter
                    spacing: Sizes.dp(80)
                    opacity: root.parkingStarted ? 1.0 : 0.0
                    Behavior on opacity { DefaultNumberAnimation {} }

                    Label {
                        text: qsTr("Zone \nParking Olympia")
                        font.weight: Font.Light
                        color: "black"
                    }

                    Label {
                        text: "1275"
                        opacity: Style.opacityLow
                        font.weight: Font.Bold
                        font.pixelSize: Sizes.fontSizeXXL
                        color: "black"
                    }
                }

                Rectangle {
                    id: ticketContent
                    property date currentTime: new Date()

                    width: parent.width / 2
                    height: Sizes.dp(425)
                    anchors.right: parent.right
                    anchors.verticalCenter: parent.verticalCenter
                    anchors.verticalCenterOffset: Sizes.dp(-12)
                    color: Style.accentColor
                    opacity: root.parkingStarted ? 1.0 : 0.0
                    Behavior on opacity { DefaultNumberAnimation {} }

                    onOpacityChanged: {
                        if (opacity === 1.0) {
                            ticketContent.currentTime = new Date()
                        }
                    }

                    Column {
                        anchors.left: parent.left
                        anchors.leftMargin: Sizes.dp(60)
                        anchors.top: parent.top
                        anchors.topMargin: Sizes.dp(80)
                        spacing: Sizes.dp(45)

                        Label {

                            text: "Started: \ntoday " + Qt.formatDateTime(ticketContent.currentTime, "hh:mm")
                            font.weight: Font.Light
                            opacity: Style.opacityHigh
                            color: "black"
                        }

                        Label {
                            text: qsTr("2h, 14 minutes")
                            font.weight: Font.Light
                            opacity: Style.opacityHigh
                            color: "black"
                        }

                        Label {
                            text: "2.29 $"
                            font.weight: Font.Light
                            opacity: Style.opacityHigh
                            color: "black"
                        }
                    }
                }
            }
        }

然后,我们添加一些详细信息以指示没有购买活动停车票的情况。如果有活动的停车票,我们可以显示该停车票资产。基于Neptune 3 UI的设计,我们需要通过从右向左移动来为活动的停车票添加动画。这是通过以下代码实现的

anchors.rightMargin: root.parkingStarted ? 0 : - width * 0.85
Behavior on anchors.rightMargin { DefaultNumberAnimation {} }

我们使用默认数字动画{},这是一个预定义的动画,支持我们的需求。当停车票活动时,启用parkingStarted属性。此属性将停车票的边距及其行为应用于停车票,我们也在该组件中定义了这种行为。

填写下部内容

下部内容显示停车票的详细信息,如停车区、价格、位置,以及启动按钮以开始停车票。我们使用组件来放置所有要求的标签。

        Item {
            width: parent.width
            height: parent.height - topContent.height
            anchors.top: topContent.bottom

            Row {
                anchors.top: parent.top
                anchors.topMargin: Sizes.dp(60)
                anchors.left: parent.left
                anchors.leftMargin: Sizes.dp(50)
                spacing: Sizes.dp(200)

                Column {
                    spacing: Sizes.dp(50)

                    Label {
                        text: qsTr("Zone")
                        font.weight: Font.Light
                        opacity: Style.opacityMedium
                        font.pixelSize: Sizes.fontSizeL
                    }

                    Row {
                        spacing: Sizes.dp(60)

                        Column {
                            Label {
                                text: qsTr("Every day 12 - 22")
                                font.weight: Font.Light
                                font.pixelSize: Sizes.fontSizeS
                                opacity: Style.opacityMedium
                            }

                            Label {
                                text: qsTr("Other times")
                                font.weight: Font.Light
                                font.pixelSize: Sizes.fontSizeS
                                opacity: Style.opacityMedium
                            }

                            Label {
                                text: qsTr("Service fee")
                                font.weight: Font.Light
                                font.pixelSize: Sizes.fontSizeS
                                opacity: Style.opacityMedium
                            }
                        }

                        Column {
                            Label {
                                text: qsTr("1.5 $ / started hour")
                                font.weight: Font.Light
                                font.pixelSize: Sizes.fontSizeS
                                opacity: Style.opacityMedium
                            }

                            Label {
                                text: qsTr("1 $ / started hour")
                                font.weight: Font.Light
                                font.pixelSize: Sizes.fontSizeS
                                opacity: Style.opacityMedium
                            }

                            Label {
                                text: "0.29 $"
                                font.weight: Font.Light
                                font.pixelSize: Sizes.fontSizeS
                                opacity: Style.opacityMedium
                            }
                        }
                    }
                }

                Column {
                    spacing: Sizes.dp(250)

                    Label {
                        anchors.right: parent.right
                        text: qsTr("1275, Parking Olympia")
                        font.weight: Font.Light
                        opacity: Style.opacityMedium
                    }

                    Button {
                        id: startButton
                        implicitWidth: Sizes.dp(250)
                        implicitHeight: Sizes.dp(70)
                        font.pixelSize: Sizes.fontSizeM
                        checkable: true
                        checked: root.parkingStarted
                        text: !root.parkingStarted ? qsTr("Start") : qsTr("End (2.29 $)")

                        background: Rectangle {
                            color: {
                                if (startButton.checked) {
                                    return "red";
                                } else {
                                    return "green";
                                }
                            }
                            opacity: {
                                if (startButton.pressed) {
                                    return 0.1;
                                } else if (startButton.checked) {
                                    return 0.3;
                                } else {
                                    return 0.3;
                                }
                            }
                            Behavior on opacity { DefaultNumberAnimation {} }
                            Behavior on color { ColorAnimation { duration: 200 } }

                            radius: width / 2
                        }

                        onClicked: root.parkingStarted = !root.parkingStarted
                    }
                }
            }
        }

在上面的代码片段中,启动按钮是一个有趣的例子。由于Neptune 3 UI主要使用QtQuickControls 2,我们可以为所有按钮预先定义一个默认样式。然而,在这个停车应用中,我们自定义了我们的按钮并使用具有不同颜色和行为的背景。

注意:要查看可用的按钮类型,请在Neptune 3 UI中启动表格应用

添加清单文件

当我们准备好运行应用程序时,我们需要添加一个包含以下行的info.yaml清单文件

formatVersion: 1
formatType: am-application
---
id:      'chapter1-basics'
icon:    'icon.png'
code:    'Main.qml'
runtime: 'qml'
name:
  en: 'Parking'

我们需要为停车应用指定一个图标,用于在系统UI应用程序启动器中显示,一旦安装在不同应用程序中。有关info.yaml的更多信息,请参阅Neptune 3 UI - 应用开发清单定义

添加项目文件

接下来,我们还需要创建一个项目文件.pro,它指定停车应用项目如下

TEMPLATE = aux

FILES += info.yaml \
         icon.png \
         Main.qml

assets.files += assets/*
assets.path = $$[QT_INSTALL_EXAMPLES]/neptune3-ui/chapter1-basics/assets

app.files = $$FILES
app.path = $$[QT_INSTALL_EXAMPLES]/neptune3-ui/chapter1-basics

INSTALLS += app assets

AM_MANIFEST = info.yaml
AM_PACKAGE_DIR = $$app.path

load(am-app)

如果您使用Qt Creator并安装了Qt Creator插件用于Qt应用管理器,您可以直接在Neptune 3 UI的系统UI中部署和运行应用。为此,请按照以下步骤操作

  • 在Qt Creator中打开您的.pro文件。
  • 项目视图中,在构建和运行下,选择运行。确认您的配置值与下面显示的值匹配。

当项目准备就绪后,按Ctrl+R键在Neptune 3 UI中运行停车场应用。

注意:在部署和运行停车场应用之前,请确保Neptune 3 UI正在运行。

第二章:扩展停车场应用并集成意图和通知

在本章中,我们将学习如何扩展我们的停车场应用并集成意图和通知。目前,该应用仅显示静态数据,并允许您以最少的动画开始和停止停车会话。

从Qt应用管理器集成意图

Qt应用管理器能够通过发送信号并期望得到响应值(信息)的方式,使一个应用能够与另一个应用或系统UI进行通信。

假设我们需要能够调用一个假想的Neptune支持团队,该团队管理停车票服务。我们可以添加一个按钮来进行这样的调用。记住,在Neptune 3 UI中,有一个内置的电话应用。我们可以向电话应用发送命令来发起这个电话。

让我们先添加一个新的电话按钮

            Button {
                implicitWidth: Sizes.dp(250)
                implicitHeight: Sizes.dp(70)

                anchors.left: parent.left
                anchors.leftMargin: Sizes.dp(100)
                anchors.top: parent.top
                anchors.topMargin: Sizes.dp(340)

                font.pixelSize: Sizes.fontSizeM
                text: qsTr("Call for support")

                onClicked: sendIntent();

                function sendIntent() {
                    var appId = "com.pelagicore.phone";
                    var request = IntentClient.sendIntentRequest("call-support", appId, {});
                    request.onReplyReceived.connect(function() {
                        if (request.succeeded) {
                            var result = request.result
                            console.log(Logging.apps, "Intent result: " + result.done)
                        } else {
                            console.log(Logging.apps, "Intent request failed: " + request.errorMessage)
                        }
                    });
                }
            }

点击此按钮时,它会向电话应用发送一个call-support请求,并调用Neptune支持团队。由于我们期望收到回复消息,电话应用会发送一个回复,指出命令是否成功接收。

为了使电话应用能够接收意图请求,需要有一个意图处理器可用。

此外,此"call-support"意图必须在info.yaml文件中注册。

formatVersion: 1
formatType: am-package
---
id:      'com.pelagicore.phone'
icon:    'icon.png'
name:
  en: 'Phone'
  de: 'Telefon'
  cs: 'Telefon'
  ru: 'Телефон'
  zh: '电话'
  ja: '電話'
  ko: '전화'

applications:
- id:      'com.pelagicore.phone'
  code:    'Main.qml'
  runtime: 'qml'
  applicationProperties: { private: { squishPort: 7728 } }

intents:
- id: call-support
- id: activate-app

categories: [ 'phone', 'widget' ]

如上所示的电话应用info.yaml文件所示,已注册了"call-support"。然后您需要在其存储中添加Intent处理器。

    readonly property IntentHandler intentHandler: IntentHandler {
        intentIds: ["call-support", "activate-app"]
        onRequestReceived: {
            switch (request.intentId) {
            case "call-support":
                root.startCall("neptunesupport");
                request.sendReply({ "done": true });
                break;
            case "activate-app":
                root.requestRaiseAppReceived()
                request.sendReply({ "done": true })
                break;
            }
        }
    }

上面的代码运行了startCall()函数,并在接收到来自我们停车场应用的意图时调用Neptune支持团队。此函数还会发送回复,指示请求的操作是否完成。此外,确保您导入了QtApplicationManager.Application 2.0以使用IntentHandler

创建通知

Qt应用管理器允许应用创建要发送并在系统UI中显示的通知。通常,系统UI有一个通知中心,存储所有创建的通知。在Neptune 3 UI中,有两种类型的通知:粘性和非粘性。当创建通知时,它会在UI顶部显示几秒钟。如果该通知是粘性的,则会在通知中心中保存。用户可以决定保留这些通知或删除每一个。

要创建通知,首先,您需要导入QtApplicationManager 2.0。然后,您可以在停车场应用中创建一个Notification对象。假设您希望通知用户停车时间将在5分钟后结束。您可以使用以下方式创建具有一些信息的Notification对象

        Notification {
            id: parkingNotification
            summary: qsTr("Your parking period is about to end")
            body: qsTr("Your parking period will be ended in 5 minutes.
                         Please extend your parking ticket or move your car.")
            sticky: true
        }

创建此通知对象后,您需要添加一个条件,当停车时间超过5分钟后触发。由于我们目前只有静态数据,您可以使用计时器来模拟这种行为。

        Timer {
            interval: 10000; running: root.parkingStarted;
            onTriggered: {
                   root.parkingStarted = false;
                   parkingNotification.show();
            }
        }

当用户按下开始按钮时,此计时器模拟停车券的持续时间。10秒后,计时器被触发,并显示通知。它还将重置parkingStarted属性。

第三章:通过中间件API和模拟扩展停车场应用

在前几章中,我们已学过了UI以及与Neptune 3 UI良好集成的必要组件。

在本章中,我们将学习如何通过中间件API扩展停车应用程序,并提供一个模拟,显示当前可用的停车场地数量。

虽然本章介绍了中间件集成、其工作原理以及如何正确打包,但完整深入探讨超出了本章范围。有关如何开发中间件API的更详细信息,请参阅Qt IVI生成器教程

注意:此应用程序需要一种多进程环境。

定义中间件API

为了定义我们的中间件API,我们使用QtIvi模块中的IVI生成器。此生成器使用接口定义语言(IDL)来生成代码,大大减少了我们需要编写的代码量。

QFace

QtIvi使用QFace IDL来描述需要生成的内容。对于本示例,我们在一个名为Parking的模块中定义了一个简单的接口ParkingInfo,其中包含一个名为freeLots的只读属性。

@config_simulator: { simulationFile: "qrc:/simulation.qml" }
module Example.Parking 1.0;

interface ParkingInfo {
    @config_simulator: { default: 42 }
    readonly int freeLots
}
自动生成

现在,我们的IDL文件已准备好第一版,是时候使用IVI生成器工具从它中自动生成API了。类似于moc,此自动生成过程已集成到qmake构建系统,并在编译时完成。

以下.pro文件基于我们的IDL文件构建一个C++库

TARGET = $$qtLibraryTarget(Parking)
TEMPLATE = lib
DESTDIR = ..

QT += ivicore ivicore-private qml quick
CONFIG += unversioned_libname unversioned_soname

DEFINES += QT_BUILD_EXAMPLE_PARKING_LIB
CONFIG += ivigenerator
QFACE_SOURCES = ../parking.qface

macos: QMAKE_SONAME_PREFIX = @rpath

target.path = $$[QT_INSTALL_EXAMPLES]/neptune3-ui/chapter3-middleware/
INSTALLS += target

通过将ivigenerator添加到CONFIG变量中,qmake的IVI生成器集成被加载,并且它期望在Create QFACE_SOURCES变量中有QFace IDL文件。设置DEFINE确保库导出其符号,这在Windows系统中是必要的。

哪些文件被自动生成

IVI生成器基于生成模板工作--它们定义了从QFace文件生成什么内容。如果没有定义QFACE_FORMAT,则使用默认模板frontend。有关这些模板的更多详细信息,请参阅使用生成器

frontend模板生成以下内容

  • 对于QFace文件中的每个接口,从QIviAbstractFeature派生出一个C++类
  • 一个模块类,帮助将所有接口注册到QML中,并存储全局类型和函数

这些文件位于您库的构建文件夹中,如果您想检查C++代码。

QML插件

除了包含我们的中间件API的库之外,我们还需要一个QML插件,才能在QML中使用该API。

IVI生成器可以帮助我们使用不同的生成模板生成此类插件。以下.pro文件生成了一个将API导出到QML的QML插件

TEMPLATE = lib
CONFIG += plugin
QT += ivicore

LIBS += -L$$OUT_PWD/../ -l$$qtLibraryTarget(Parking)
INCLUDEPATH += $$OUT_PWD/../frontend
QMAKE_RPATHDIR += $$QMAKE_REL_RPATH_BASE/../../../

QFACE_FORMAT = qmlplugin
QFACE_SOURCES = ../parking.qface

load(ivigenerator)

DESTDIR = $$OUT_PWD/$$replace(URI, \\., /)

exists($$OUT_PWD/qmldir) {
    cpqmldir.files = $$OUT_PWD/qmldir \
                     $$OUT_PWD/plugins.qmltypes
    cpqmldir.path = $$DESTDIR
    cpqmldir.CONFIG = no_check_exist
    COPIES += cpqmldir

    installPath = $$[QT_INSTALL_EXAMPLES]/neptune3-ui/chapter3-middleware/imports/$$replace(URI, \\., /)
    qmldir.files = $$OUT_PWD/qmldir \
                   $$OUT_PWD/plugins.qmltypes
    qmldir.path = $$installPath
    target.path = $$installPath
    INSTALLS += target qmldir
}

我们使用CONFIG构建一个插件,然后定义链接器的设置,以便将其与我们的前端库链接。接下来,我们使用QFACE_FORMAT选择qmlplugin作为生成模板。这次我们没有将ivigenerator添加到CONFIG中,而是使用qmake的load()函数显式加载该功能。这样,我们可以使用URI变量,它是qmlplugin生成模板的一部分。此变量可以定义一个DESTDIR,通过将所有点替换为斜杠。

除了文件夹结构外,QmlEngine还需要一个qmldir文件,该文件指示哪些文件是插件的一部分,以及它们属于哪个URI。更多详细信息,请参阅模块定义qmldir文件

这两个文件(qmldirplugins.qmltypes)都由IVI生成器自动生成,提供了关于代码补全的信息;但它们需要放置在库旁边。为此,我们把这些文件添加到类似于INSTALL目标的作用域中,但将其添加到COPIES变量中。这样确保在构建插件时文件被复制。

QML集成

在生成了我们的中间件API及其相应的QML插件后,就到了将我们的新API集成到停车应用中的时候了。

对于QML插件,我们的IDL文件中的模块名称用作导入URI;默认导入版本是1.0。我们的main.qml文件的导入语句如下所示:

import Example.Parking 1.0

默认情况下,QML API使用与我们的IDL文件中接口相同的名称。有关如何使用自定义名称或导入URI的更多信息,请参阅使用生成器

现在我们可以实例化我们的接口,并给它设置一个ID,就像对任何其他QML元素一样

        ParkingInfo {
            id: parkingInfo
        }

要显示当前可用的停车场,我们需要创建一个QML绑定,使用我们新添加的ParkingInfo QML元素中的freeLots属性

                        text: parkingInfo.freeLots + qsTr(", Parking Olympia")
打包所需的调整

对于一个普通的Qt QML应用,这些步骤现在就足以启动应用并查看空闲停车位的数量为0,因为它是初始化为默认值。但因为我们正在开发Neptune 3 UI的应用程序并打算在Neptune 3 UI运行时打包和安装它,所以需要一些额外的步骤。

通常在构建库时,会创建两个符号链接以允许在不重新编译其他应用程序的情况下进行版本升级。但在ApplicationManager包中,出于安全原因不允许符号链接。因此,以下qmake CONFIG需要设置以确保不创建这些符号链接;在链接到库时也不确认它们

CONFIG += unversioned_libname unversioned_soname

为了我们的QML插件能够正确运行,我们需要将一个额外的导入路径设置到qmlengine。通常,这是通过使用QML2_IMPORT_PATH环境变量,将其传递给qmlscene,或在您的main.cpp中使用QQmlEngine::addImportPath()来完成的。但是,因为ApplicationManager在安装后启动应用程序,而且我们没有打包自己的main.cpp文件,所以我们需要在包清单info.yaml中定义这些设置。对于导入路径,我们添加以下行

runtimeParameters:
    importPaths: [ 'imports' ]

设置了这些设置后,该应用就可以部署了。它应该显示0个空闲停车位

定义一个模拟行为

为了模拟我们中间件API的一些值,首先我们需要更好地理解QtIvi的架构。正如我们在生成库时所学的,IVI生成器使用了一个名为frontend的模板。为了定义一些模拟值或连接到真实的API,我们还需要相应的backend。这个backend以一种插件的格式提供,而QtIvi负责将frontend连接到backend。有关此概念的更多信息,请参阅动态后端架构。

具有静态值的后端插件

下一步是使用IVI生成器生成这样的后端,并使用注释定义模拟应该执行的操作。

让我们从.pro开始,生成和构建我们的后端

TEMPLATE = lib
TARGET = $$qtLibraryTarget(parking_simulation)
DESTDIR = ../qtivi

QT += core ivicore
CONFIG += ivigenerator plugin

LIBS += -L$$OUT_PWD/../ -l$$qtLibraryTarget(Parking)
INCLUDEPATH += $$OUT_PWD/../frontend
QMAKE_RPATHDIR += $$QMAKE_REL_RPATH_BASE/../

QFACE_FORMAT = backend_simulator
QFACE_SOURCES = ../parking.qface
PLUGIN_TYPE = qtivi

# Additional import path used to resolve QML modules in Qt Creator's code model
QML_IMPORT_PATH = $$OUT_PWD/../frontend/qml

target.path = $$[QT_INSTALL_EXAMPLES]/neptune3-ui/chapter3-middleware/qtivi
INSTALLS += target

RESOURCES += \
    simulation.qrc

要构建一个插件,我们需要在CONFIG变量中添加plugin并将其更改QFACE_FORMAT为使用backend_simulator生成模板。与QML插件类似,后端也需要链接到我们的前端库,因为它使用那里定义的类型。

为了确保QtIvi可以找到后端,它需要放置在qtivi文件夹中。反过来,这个文件夹需要成为Qt插件搜索路径的一部分。

就像导入路径一样,附加的插件路径需要在包清单中进行设置

runtimeParameters:
    pluginPaths: [ '.' ]

现在,我们已经创建了一个模拟后端,但是没有额外的信息,IVI生成器无法创建真正有用的东西。

首先,我们定义一个静态默认值,该默认值由模拟后端提供。最简单的方法是在我们的QFace IDL文件中使用注释。注释是一种特殊的注释类型,它为生成模板提供了额外信息,说明应该生成什么。为了定义默认值,我们这样更改IDL文件

module Example.Parking 1.0;

interface ParkingInfo {
    @config_simulator: { default: 42 }
    readonly int freeLots
}

由于IDL文件的变化,现在IVI生成器重新创建了后端插件。当运行更新后的应用程序时,我们应该看到免费停车位的数量为42

模拟QML

虽然注释定义默认值和提供静态模拟是有用的,但生成的模拟后端可以做更多。它还可以允许您定义更动态的模拟行为。

为了实现这一点,我们将在QFace IDL文件中添加另一个注释,并定义一个simulationFile。此文件包含我们的模拟行为,而QIviSimulationEngine则加载它。类似于其他QML文件,最佳方法是将此文件嵌入到Qt资源中。

我们的simulation.qml看起来是这样的

import QtQuick 2.10
import Example.Parking.simulation 1.0

QtObject {
    property var settings : IviSimulator.findData(IviSimulator.simulationData, "ParkingInfo")
    property bool defaultInitialized: false
    property LoggingCategory qLcParkingInfo: LoggingCategory {
        name: "example.parking.simulation.parkinginfobackend"
    }
    property var backend : ParkingInfoBackend {

        function initialize() {
            console.log(qLcParkingInfo, "INITIALIZE")
            if (!defaultInitialized) {
                IviSimulator.initializeDefault(settings, backend)
                defaultInitialized = true
            }
            Base.initialize()
        }

        property var timer: Timer {
            interval: 5000
            running: true
            repeat: true
            onTriggered: {
                var min = Math.ceil(-5)
                var max = Math.floor(5)
                var delta = Math.floor(Math.random() * (max - min +1)) + min;

                var newValue = Math.max(0, backend.freeLots + delta);

                backend.freeLots = newValue;
            }
        }
    }
}

首先,有一个settings属性,它使用了IviSimulator.findData方法的返回值进行初始化,该方法接收IviSimulator.simulationData和一个字符串作为输入。The simulationData是作为JavaScript对象的JSON文件。

findData方法帮助我们从这种界面中提取有关仪器的数据,InstrumentCluster。随后的属性帮助该界面了解是否设置了默认值。The LoggingCategory用于识别来自此模拟文件的日志输出。

之后,实际的行为是通过实例化一个InstrumentClusterBackend项目并通过更多功能对其进行扩展而定义的。The InstrumentClusterBackend是我们InstrumentCluster QML前端类的接口。除了前端之外,这些属性也是可写的,以便可以将它们更改以提供有用的模拟。

每次前端实例连接到后端时,都会调用initialize()函数。这同样适用于QML模拟:因为initialize() C++函数将这个调用转发给QML实例。这种行为也适用于所有其他函数,例如getter和setter。有关更多详细信息,请参见QIviSimulationEngine

在QML的initialize()函数内部,我们调用IviSimulator.initializeDefault(),从simulationData对象中读取默认值并初始化所有属性。这仅一次进行,因为我们不希望属性在被下一个前端实例连接到后端时重置为默认值。最后,调用基础实现以确保向前端发送initializeationDone信号。

接下来,我们通过创建一个每5秒触发一次的定时器元素来定义实际的模拟行为。在触发信号的绑定中,我们使用Math.random()函数获取介于-5和5之间的随机值,并将其添加到后端的可用停车位中,使用我们后端的freeLots属性。此值的更改会自动填充到前端,从而模拟真实的车位。

文件

图像

©2019 Luxoft Sweden AB。此处包含的文档贡献是各自所有者的版权。
此处提供的文档根据Free Software Foundation发布的GNU自由文档许可版1.3项下的条款进行许可。
Qt和相应的标志是芬兰Qt Company Ltd.和其他国家和地区商标。