彩票走势图

如何用Visual Studio创建一个嵌入式应用?Qt框架可以做到!(2/2)

转帖|使用教程|编辑:龚雪|2022-08-30 10:14:33.087|阅读 156 次

概述:本文将为大家介绍如何在Visual Studio中创建嵌入式Qt Quick应用程序,欢迎加入社群一起交流讨论!

# 慧都年终大促·界面/图表报表/文档/IDE等千款热门软控件火热促销中 >>

相关链接:

在上文中(点击这里查看),我们展示了如何在Visual Studio中针对Windows和嵌入式Linux创建多平台Qt Quick应用程序项目。现在,我们将演示如何在嵌入式设备上运行该应用程序。然后,我们将继续开发该项目,实现预定的完整嵌入式应用程序。最后,我们将使用VS调试器对应用的C ++和QML代码进行远程调试。

Qt技术交流群:166830288      欢迎一起进群讨论

在嵌入式设备上运行

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


main.qml

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");
}
}
}


Qt Quick "Hello World"

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

Visual Studio构建输出


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 ==========


VS中交叉编译Qt项目

现在我们把应用程序的二进制文件上传到树莓派。构建输出窗口显示了生成的二进制文件的位置(倒数第二行)。

Windows命令提示


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>


将应用程序二进制文件上传到目标设备

要在为了在每次构建结束时自动复制应用程序文件,可以在“ WSL Post-Build Event”属性页中设置如下命令(注意: 这将以明文形式保存设备密码)

Project Properties > WSL Post-Build Event > Command Line

curl --insecure --user pi:<password> -T /mnt/$(TargetPath.Replace('\','/').Replace(':','').ToLower()) scp://<;device-addr>/home/pi/$(TargetFileName)

在每次构建结束时将二进制文件复制到设备端

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

  • Qt二进制文件安装的路径。
  • :QPA平台插件。
  • :QPA平台插件安装的路径。
  • :物理屏幕的宽度和高度,以毫米为单位。
  • :安装的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


树莓派显示器

在Visual Studio中创建嵌入式Qt Quick应用程序

在树莓派上运行“ Hello World”应用程序

应用程序开发过程

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

  • 当前时间
  • 当前日期
  • 重要公共纪念日
  • 天气预报
  • 下一班公共交通工具
  • 新闻

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

  • 选择"Project > Add New Item.. > Qt > QML Module Definition"。
  • 在位置字段中,设置包含QML文件的文件夹路径。
在Visual Studio中创建嵌入式Qt Quick应用程序

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

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

在Visual Studio中创建嵌入式Qt Quick应用程序

将QML类型映射到源文件

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

  • 选择“Project > Add New Item... > Qt > QML File"”。
  • 将位置设置为qmldir同级目录。
  • 设置QML文件名。
  • 按“Add”。
在Visual Studio中创建嵌入式Qt Quick应用程序

向项目添加新的QML文件

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


Text {
font.family: FontFamily_Clock
font.styleName: FontStyle_Clock
font.pointSize: 144
color: "white"
renderType: Text.NativeRendering
antialiasing: false
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();
}
}


Clock QML类型的定义

该Calendar类型将显示当前日期,并在不同城市名之间循环。


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 }
}
}
}


Calendar QML类型的定义

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


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);
}
}
}
}


ApiCall QML类型的定义

创建Process的C ++类

  • 选择"Project > Add Qt Class > Qt Class"
  • 将类名设置为Process
  • 按“Add”
在Visual Studio中创建嵌入式Qt Quick应用程序

向项目添加新的Qt C ++类

Process.h


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<int, QProcess::ExitStatus>::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>("Process", 1, 0, "Process");
...


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: "//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 = "<sup>†</sup>" + 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
}
}
}
}


最后,所有QML文件和qmldir文件必须添加到应用程序的资源文件中:

  • 双击项目树中的QRC文件
  • 在“Qt Resource Editor”窗口中,按“Add > Add Files”
  • 选择所有QML文件和qmldir文件
  • 在Qt Resource Editor中按“Save”
在Visual Studio中创建嵌入式Qt Quick应用程序

QML文件和qmldir已添加到资源文件

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

在Visual Studio中创建嵌入式Qt Quick应用程序

在树莓派上运行的应用程序

在Visual Studio中进行调试

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

在Visual Studio中创建嵌入式Qt Quick应用程序

使用gdb 和gdbserver从Visual Studio进行远程调试

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

在Visual Studio中创建嵌入式Qt Quick应用程序

在Visual Studio中设置远程调试会话,必须向gdb传递两个附加命令。在“GDB Debugger”属性页面中进行配置。

Project Properties > Debugging > Additional Debugger Commands

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

在Visual Studio中创建嵌入式Qt Quick应用程序

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

Raspberry Pi Command Shell


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将启动远程调试会话。

在Visual Studio中创建嵌入式Qt Quick应用程序
远程QML调试

在嵌入式设备上运行应用程序时,也可以。

  • 在Qt设置中启用QML调试:Project Properties > Qt Project Settings
在Visual Studio中创建嵌入式Qt Quick应用程序
  • 设置应用程序启动参数,启动
  • 设置启动QML调试会话的程序参数

Project Properties > Debugging > Program Arguments

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

在Visual Studio中创建嵌入式Qt Quick应用程序
总结

我们展示了如何使用Qt VS Tools扩展在Visual Studio中创建基于Qt Quick技术的多平台嵌入式应用程序。包括:

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

该项目,包括所有源代码,可在获得。

在Visual Studio中创建嵌入式Qt Quick应用程序

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

本文转载自


Qt技术交流群:166830288      欢迎一起进群讨论


标签:

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

文章转载自:

为你推荐

  • 推荐视频
  • 推荐活动
  • 推荐产品
  • 推荐文章
  • 慧都慧问
扫码咨询


添加微信 立即咨询

电话咨询

客服热线
023-68661681

TOP