如何在Visual Studio中创建嵌入式Qt quick应用程序(下)

翻译|使用教程|编辑:鲍佳佳|2020-10-12 10:42:22.520|阅读 113 次

概述:在前文中我们展示了如何在Visual Studio中针对Windows和嵌入式Linux创建多平台Qt Quick应用程序项目。现在,我们将展示如何在嵌入式设备上运行该应用程序。然后,我们将继续将该项目开发为我们打算创建的完整嵌入式应用程序。最后,我们将使用VS调试器对应用程序的C ++和QML代码进行远程调试。

# 31款JAVA开发必备控件和工具 # 企业数字化建设合规无风险[专题]

相关链接:

Qt是一个跨平台框架,通常用作图形工具包,它不仅创建CLI应用程序中非常有用。而且它也可以在三种主要的台式机操作系统以及移动操作系统(如Symbian,Nokia Belle,Meego Harmattan,MeeGo或BB10)以及嵌入式设备,Android(Necessitas)和iOS的端口上运行。现在我们为你提供了免费的试用版。赶快点击下载Qt最新试用版>>

点击获取更多文章教程

Qtitan组件集

  • QtitanRibbon| 下载试用: 遵循Microsoft Ribbon UI Paradigm for Qt技术的Ribbon UI组件,致力于为Windows、Linux和Mac OS X提供功能完整的Ribbon组件。
  • QtitanChart | 下载试用 : 是一个C ++库,代表一组控件,这些控件使您可以快速地为应用程序提供漂亮而丰富的图表。并且支持所有主要的桌面操作系统。
  • QtitanDataGrid| 下载试用 : 适用于Qt的商业化DataGrid 组件,使得表格数据可以直接面向终端用户完全集成了QtDesigner,极易适应其他相似开发环境,保证100%兼容Qt GUI。

在本文的第1部分中,我们展示了如何在Visual Studio中针对Windows和嵌入式Linux创建多平台Qt Quick应用程序项目。现在,我们将展示如何在嵌入式设备上运行该应用程序。然后,我们将继续将该项目开发为我们打算创建的完整嵌入式应用程序。最后,我们将使用VS调试器对应用程序的C ++和QML代码进行远程调试。

在嵌入式设备上运行

我们已经展示了如何交叉编译在Visual Studio中创建的“ hello world” Qt Quick应用程序。现在,我们将看到如何在Raspberry Pi上运行该应用程序。由于我们将以全屏模式运行,因此我们必须首先向应用程序窗口中添加一些内容。

Window {
    visible: true
    title: qsTr("Hello World")
    Text {
        id: clock
        font.pointSize: 72
        Timer {
            interval: 1000; running: true; repeat: true
            onTriggered: clock.text = (new Date).toLocaleTimeString(Qt.locale("de_DE"), "hh:mm:ss");
        }
    }
}

和以前一样,选择Linux项目配置,然后按F7键开始交叉编译。

1>------ Build started: Project: QuickMirror, Configuration: Debug_RPi x64 ------
1>rcc qml.qrc
1>Invoking 'mkdir -p $(dirname qml.qrc); mkdir -p $(dirname /mnt/c/Users/user/Source/Repos/QuickMirror/main.qml); mkdir -p $(dirname /mnt/c/Users/user/Source/Repos/QuickMirror/obj/x64/Debug_RPi/rcc/qrc_qml.cpp); (/home/user/raspi/qt5/bin/rcc /mnt/c/Users/user/Source/Repos/QuickMirror/qml.qrc --name qml -o /mnt/c/Users/user/Source/Repos/QuickMirror/obj/x64/Debug_RPi/rcc/qrc_qml.cpp)', working directory: '/mnt/c/Users/user/Source/Repos/QuickMirror'
1>Starting remote build
1>Compiling sources:
1>qrc_qml.cpp
1>Linking objects
1>QuickMirror.vcxproj -> C:\Users\user\Source\Repos\QuickMirror\bin\x64\Debug_RPi\QuickMirror.out
========== Build: 1 succeeded, 0 failed, 0 up-to-date, 0 skipped ==========

要在每次构建结束时自动复制应用程序文件,可以在“ WSL构建后事件”属性页中设置以下命令(ATTN: 这将以明文形式保存设备密码)。

C:\Users\user> scp C:\Users\user\Source\Repos\QuickMirror\bin\x64\Debug_RPi\QuickMirror.out pi@192.168.1.98:/home/pi/

pi@192.168.1.98's password:

QuickMirror.out 100% 465KB 1.6MB/s 00:00

C:\Users\user>

在启动Qt Quick应用程序之前,我们需要设置一些必需的环境变量:

  • LD_LIBRARY_PATH
    Qt二进制文件安装目录的路径。

  • QT_QPA_PLATFORM
    平台插件。

  • QT_QPA_PLATFORM_PLUGIN_PATH
    平台插件安装目录的路径。

  • QT_QPA_EGLFS_PHYSICAL_WIDTH
    QT_QPA_EGLFS_PHYSICAL_HEIGHT

    物理屏幕的宽度和高度,以毫米为单位。

  • QML2_IMPORT_PATH
    安装的QML模块的路径。
pi@raspberry-pi:~$ export LD_LIBRARY_PATH="/usr/local/qt5pi/lib"
pi@raspberry-pi:~$ export QT_QPA_PLATFORM="eglfs"
pi@raspberry-pi:~$ export QT_QPA_PLATFORM_PLUGIN_PATH="/usr/local/qt5pi/plugins/platforms"
pi@raspberry-pi:~$ export QT_QPA_EGLFS_PHYSICAL_WIDTH="326"
pi@raspberry-pi:~$ export QT_QPA_EGLFS_PHYSICAL_HEIGHT="520"
pi@raspberry-pi:~$ export QML2_IMPORT_PATH="/usr/local/qt5pi/qml"
pi@raspberry-pi:~$ ./QuickMirror.out

树莓派显示器

在Raspberry Pi中运行“ Hello World”应用程序

开发应用程序

我们的应用程序的要求包括显示以下信息:

  • 当前时间
  • 当前日期
  • 周年纪念
  • 天气预报
  • 下次出发的公共交通工具
  • 新闻

我们将把每个项目封装为专用的QML类型。为此,我们必须首先将QML模块定义(qmldir)文件添加到项目中:

  • 选择“项目>添加新项。。> Qt> QML模块定义”。
  • 在位置字段中,指示将包含QML文件的文件夹的路径。

向项目添加新的QML模块定义

按下“添加”后,qmldir 文件将在项目树中可用。我们将使用此文件来定义每种QML类型到其对应源文件的映射。

ApiCall         1.0 QuickMirror.ApiCall.qml
Calendar        1.0 QuickMirror.Calendar.qml
Clock           1.0 QuickMirror.Clock.qml
NewsTicker      1.0 QuickMirror.NewsTicker.qml
OnThisDay       1.0 QuickMirror.OnThisDay.qml
PublicTransport 1.0 QuickMirror.PublicTransport.qml
Weather         1.0 QuickMirror.Weather.qml

要将新的QML源文件添加到项目中:

  • 选择“项目>添加新项...> Qt> QML文件”。
  • 将位置设置qmldir为创建文件的相同目录。
  • 设置QML文件名。
  • 按“添加”。


我们将首先添加QML类型以显示当前时间,当前日期和重要的周年纪念日。该Clock类型将显示当前时间,每秒刷新一次。

QuickMirror.Clock.qml

  function refresh() {
        text = (new Date).toLocaleTimeString(Qt.locale("de_DE"), "hh:mm");
    }
    Component.onCompleted : refresh();
    Timer {
        interval: 1000; running: true; repeat: true onTriggered: parent.refresh();
    }
} 

Calendar类型将显示当前日期,并在不同语言环境之间循环。

QuickMirror.Calendar.qml

Text {
    renderType: Text.NativeRendering
    id: calendar
    color: "white"
    font.family: FontFamily_Bold
    font.styleName: FontStyle_Bold
    font.pointSize: 72
    property var locales: ["en_US", "de_DE", "pt_PT"]
    property var localeIdx: 0
    function capitalize(s) {
        return s.replace(/(^|-)./g, function(c) { return c.toUpperCase(); });
    }
    function setNextLocale() {
        localeIdx = (localeIdx + 1) % locales.length;
    }
    function getCurrentText() {
        var date = new Date;
        var locale = Qt.locale(locales[localeIdx]);
        var calendarText = capitalize(date.toLocaleDateString(locale, "dddd, dd"));
        var monthShort = date.toLocaleDateString(locale, "MMM");
        var monthLong = date.toLocaleDateString(locale, "MMMM");
        if (monthLong.length <= 5) { calendarText += capitalize(monthLong); } else { calendarText += capitalize(monthShort); if (!monthShort.endsWith(".")) calendarText += "."; } calendarText += date.toLocaleDateString(locale, " yyyy"); return calendarText; } Component.onCompleted: { text = getCurrentText(); } Timer { interval: 15000; running: true; repeat: true onTriggered: { setNextLocale(); text = getCurrentText(); } } Behavior on text { SequentialAnimation { NumberAnimation { target: calendar; property: "opacity"; to: 0.0; duration: 1000 } PropertyAction { target: calendar; property: "text" } NumberAnimation { target: calendar; property: "opacity"; to: 1.0; duration: 500 } } } }

除了日期/时间,我们的应用程序还将依靠Web API来检索信息。我们将curl在一个单独的过程中运行以连接到Web API。流程创建将由名为的C ++类处理Process。然后,QML类型ApiCall将使用一个Process对象以curl必要的参数开始并收集其输出。

QuickMirror.ApiCall.qml

Item {
    property var url: ""
    property var path: []
    property var query: []
    signal response(var response)
    signal error(var error)
    Process {
        id: curl
        property var path: Q_OS_WIN ? "C:\\Windows\\System32\\curl.exe" : "/usr/bin/curl"
        property var request: ""
        command: path + " -s \"" + request + "\""
    }
    function sendRequest() {
        curl.request = url;
        if (path.length > 0)
            curl.request += "/" + path.join("/");
         if (query.length > 0)
            curl.request += "?" + query.join("&");
        curl.start();
    }
    Connections {
        target: curl
        onExit /*(int exitCode, QByteArray processOutput)*/ : {
            if (exitCode != 0) {
                console.log("ApiCall: exit " + exitCode);
                console.log("==== ApiCall: request: " + curl.request);
                return error("exit " + exitCode);
            }
            try {
                return response(JSON.parse(processOutput));
            } catch (err) {
                console.log("ApiCall: error: " + err.toString());
                console.log("==== ApiCall: request: " + curl.request);
                console.log("==== ApiCall: response: " + processOutput);
                return error(err);
            }
        }
    }
}

要创建ProcessC ++类:

  • 选择“项目>添加Qt类> Qt类”
  • 将类名设置为Process
  • 按“添加”


class Process : public QProcess
{
    Q_OBJECT
    Q_PROPERTY(QString command READ command WRITE setCommand NOTIFY commandChanged)

public:
    Process(QObject* parent = 0);
    ~Process();

public:
    Q_INVOKABLE void start();
    void setCommand(const QString& cmd);
    QString command() const;

signals:
    void commandChanged();
    void exit(int exitCode, QByteArray processOutput);

protected:
    void onFinished(int exitCode, QProcess::ExitStatus status);
    void onErrorOccurred(QProcess::ProcessError error);

private:
    QString m_command;
};

Process.cpp

Process(QObject* parent) : QProcess(parent)
{
    connect(
        this, QOverload::of(&QProcess::finished),
        this, &Process::onFinished);
    connect(
        this, &QProcess::errorOccurred,
        this, &Process::onErrorOccurred);
}

Process::~Process()
{
}

void Process::setCommand(const QString& cmd)
{
    if (cmd != m_command) {
        m_command = cmd;
        emit commandChanged();
    }
}

QString Process::command() const
{
    return m_command;
}

void Process::start()
{
    if (state() == ProcessState::NotRunning)
        QProcess::start(m_command);
    else
        qInfo() << "==== QProcess: ERROR already running:" << m_command; } void Process::onFinished(int exitCode, QProcess::ExitStatus status) { emit exit((status == ExitStatus::NormalExit) ? exitCode : -1, readAll()); } void Process::onErrorOccurred(QProcess::ProcessError error) { qInfo() << "==== QProcess: ERROR " << error; }

main.cpp

int main(int argc, char* argv[])
{
    qmlRegisterType("Process", 1, 0, "Process");
...

OnThisDay QML类型将使用的实例,通过它们来获取显着的纪念日列表和循环每隔几秒钟。 ApiCall

QuickMirror.OnThisDay.qml

Item {
    id: onThisDay
    clip: true
    property int viewportHeight
    property var events: []
    property var births: []
    property var deaths: []
    property int idxEventType: -1
    ApiCall {
        id: onThisDayApi
        property int month: 0
        property int day: 0
        property string eventType: ""
        url: "https://byabbe.se"; path: ["on-this-day", month, day, eventType + ".json" ]
        onResponse: {
            if ("events" in response) {
                events = shuffle(response.events);
                eventType = "births";
                sendRequest();
            } else if ("births" in response) {
                births = shuffle(response.births);
                for (var i in births)
                    births[i].year = "*" + births[i].year;
                eventType = "deaths";
                sendRequest();
            } else if ("deaths" in response) {
                deaths = shuffle(response.deaths);
                for (var i in deaths)
                    deaths[i].year = "" + deaths[i].year;
                next();
            }
        }
    }
    function init() {
        events = [];
        births = [];
        deaths = [];
        idxEventType = -1;
        var today = new Date;
        onThisDayApi.month = today.getMonth() + 1;
        onThisDayApi.day = today.getDate();
        onThisDayApi.eventType = "events";
        onThisDayApi.sendRequest();
    }
    function next() {
        if (events.length + births.length + deaths.length == 0)
            return;
        var today = new Date;
        if (onThisDayApi.month != today.getMonth() + 1 || onThisDayApi.day != today.getDate())
            return init();
        onThisDayText.color = "white";
        idxEventType = (idxEventType + 1) % 3;
        var event;
        switch (idxEventType) {
            case 0:
                if (events.length == 0)
                    return next();
                event = events.shift();
                events = shuffle(events);
                events.push(event);
                break;
            case 1:
                if (births.length == 0)
                    return next();
                event = births.shift();
                births = shuffle(births);
                births.push(event);
                break;
            case 2:
                if (deaths.length == 0)
                    return next();
                event = deaths.shift();
                deaths = shuffle(deaths);
                deaths.push(event);
                break;
        }
        onThisDayText.text = event.year + " – " + event.description;
        showText.start();
    }
    Component.onCompleted: {
        init();
    }
    Timer {
        id: timerRetry
        interval: 10000; running: true; repeat: true
        onTriggered: {
            if (events.length + births.length + deaths.length == 0)
                init();
        }
    }
    SequentialAnimation {
        id: showText
        PropertyAction { target: onThisDayText; property: "y"; value: 25 }
        NumberAnimation { target: onThisDayText; property: "opacity"; to: 1.0; duration: 500 }
        PauseAnimation { duration: 3000 }
        NumberAnimation {
            target: onThisDayText
            property: "y"
            to: Math.min(-(25 + onThisDayText.contentHeight) + viewportHeight, 25)
            duration: Math.max(0, (Math.abs(to - from) * 1000) / 25)
        }
        PauseAnimation { duration: 3000 }
        NumberAnimation { target: onThisDayText; property: "opacity"; to: 0.0; duration: 1000 }
        onFinished: {
            onThisDay.next();
        }
    }
    Text {
        renderType: Text.NativeRendering
        id: onThisDayText
        wrapMode: Text.WordWrap
        font.family: FontFamily_Normal
        font.styleName: FontStyle_Normal
        font.pointSize: 40
        textFormat: Text.RichText
        color: "white"
        y: 25
        anchors.left: parent.left
        width: parent.width
        height: contentHeight
        opacity: 0
    }
    Rectangle {
        id: top
        anchors.top: parent.top
        anchors.left: parent.left
        width: parent.width
        height: 10
        gradient: Gradient {
            orientation: Gradient.Vertical
            GradientStop { position: 0.0; color: "black" }
            GradientStop { position: 0.5; color: "transparent" }
        }
    }
    Rectangle {
        id: bottomFade
        anchors.top: parent.top
        anchors.topMargin: viewportHeight
        anchors.left: parent.left
        width: parent.width
        height: 0.1 * viewportHeight
        gradient: Gradient {
            orientation: Gradient.Vertical
            GradientStop { position: 0.0; color: "transparent" }
            GradientStop { position: 0.5; color: "black" }
        }
    }
    Rectangle {
        anchors.top: bottomFade.bottom
        anchors.bottom: parent.bottom
        anchors.left: parent.left
        width: parent.width
        color: "black"
    }
}

现在,我们已经定义了一些应用程序的QML类型,我们将它们排列在主QML文件上。

main.qml

import "QuickMirrorTypes"

Window {
    visible: true
    title: qsTr("Quick Mirror")
    Flickable {
        anchors.fill: parent
        contentWidth: mirror.width
        contentHeight: mirror.height
        Rectangle {
            id: mirror
            width: 1080
            height: 1920
            color: "black"

            Clock {
                id: clock
                anchors.top: mirror.top
                anchors.left: mirror.left
            }

            Calendar {
                id: calendar
                anchors.top: clock.bottom
                anchors.topMargin: -20
                anchors.left: mirror.left
            }

            Rectangle {
                anchors.top: calendar.bottom
                anchors.topMargin: -5
                anchors.left: mirror.left
                width: 800
                height: 2
                color: "white"
            }

            OnThisDay {
                id: onThisDay
                anchors.top: calendar.bottom
                anchors.left: mirror.left
                anchors.leftMargin: 10
                anchors.bottom: mirror.bottom
                width: 780
                viewportHeight: 260
            }
        }
    }
}

最后,qmldir 必须将QML文件和该文件全部添加到应用程序的资源文件中:

  • 双击项目树中的QRC文件
  • 在“ Qt资源编辑器”窗口中,按“添加>添加文件”
  • 选择所有QML文件和qmldir文件
  • 在Qt资源编辑器中按“保存”

构建和部署后,我们将能够启动应用程序并查看显示的信息。

树莓派显示器

在Raspberry Pi上运行的应用程序

在Visual Studio中进行调试

VS支持通过调试在WSL上运行的应用程序gdb。要在Raspberry Pi上运行时进行调试,我们将使用启动应用程序gdbserver,然后配置gdb为连接到设备并启动远程调试会话。

为此,gdb WSL中安装的组件必须支持目标设备体系结构。一种简单的方法是安装gdb-multiarch。为了确保VS使用正确的调试器,我们将创建从gdb到的符号链接gdb-multiarch

WSL命令外壳

user@buildhost:~$ sudo apt-get install gdb-multiarch
...
user@buildhost:~$ cd /usr/bin
user@buildhost:/usr/bin$ sudo mv gdb gdb-bkup
user@buildhost:/usr/bin$ sudo ln -s gdb-multiarch gdb
user@buildhost:/usr/bin$ ls -go gdb*
lrwxrwxrwx 1 13 Sep 2 11:31 gdb -> gdb-multiarch
-rwxr-xr-x 1 8440200 Feb 11 2020 gdb-bkup
-rwxr-xr-x 1 15192808 Feb 11 2020 gdb-multiarch
user@buildhost:/usr/bin$

要在Visual Studio中设置远程调试会话,必须将两个附加命令传递给gdb。这是在“ GDB调试器”属性页面中配置的。

Project Properties > Debugging > Additional Debugger Commands

target extended-remote 192.168.1.98:2345
set remote exec-file /home/pi/QuickMirror.out

在开始远程调试会话之前,我们必须设置所需的环境变量并gdbserver在设备上启动。

Raspberry Pi命令外壳

pi@raspberry-pi:~$ export LD_LIBRARY_PATH="/usr/local/qt5pi/lib"
pi@raspberry-pi:~$ export QT_QPA_PLATFORM="eglfs"
pi@raspberry-pi:~$ export QT_QPA_PLATFORM_PLUGIN_PATH="/usr/local/qt5pi/plugins/platforms"
pi@raspberry-pi:~$ export QT_QPA_EGLFS_PHYSICAL_WIDTH="326"
pi@raspberry-pi:~$ export QT_QPA_EGLFS_PHYSICAL_HEIGHT="520"
pi@raspberry-pi:~$ export QML2_IMPORT_PATH="/usr/local/qt5pi/qml"
pi@raspberry-pi:~$ gdbserver --once --multi :2345
Listening on port 2345

按F5将启动远程调试会话。

在远程调试期间在C ++代码中的断点处停止

远程QML调试

在嵌入式设备上运行应用程序时,也可以调试QML代码。

  • 在Qt设置中启用QML调试:项目属性> Qt项目设置
  • 用于启动QML调试会话的安装程序参数

项目属性>调试>程序参数

-qmljsdebugger=port:8989,host:192.168.1.98,block


打包

我们已经展示了如何使用Qt VS Tools扩展在带有Qt Quick的Visual Studio中创建多平台嵌入式应用程序。这包括:

  • 从头开始创建Qt Quick项目
  • 用QML编写应用程序代码
  • 交叉编译应用程序
  • 在嵌入式设备上部署和运行
  • 在Visual Studio中对C ++和QML代码进行远程调试

该项目,包括所有源代码,可从以下网址获得:https : //github.com/micosta/quickmirror。

我们的应用程序在嵌入式设备上运行


应用程序在嵌入式设备上运行

感谢您的阅读和对Qt和VS Tools扩展的关注。如果您有任何疑问或建议,请在下面发表评论。

如果这篇文章没能满足你的需求、点击获取更多文章教程!现在立刻下载Qt免费试用吧!更多Qt类开发工具QtitanRibbonQtitanChartQtitanNavigationQtitanDockingQtitanDataGrid在线订购现直降1000元,欢迎咨询慧都在线客服获取更多优惠>>


标签:

本站文章除注明转载外,均为本站原创或翻译。欢迎任何形式的转载,但请务必注明出处、不得修改原文相关链接,如果存在内容上的异议请邮件反馈至hey@evget.com

文章转载自:the QT Company

为你推荐

  • 推荐视频
  • 推荐活动
  • 推荐产品
  • 推荐文章
  • 慧都慧问
在线咨询
联系我们
TOP
在线客服系统
live chat