轉(zhuǎn)帖|使用教程|編輯:龔雪|2022-08-30 10:14:33.087|閱讀 254 次
概述:本文將為大家介紹如何在Visual Studio中創(chuàng)建嵌入式Qt Quick應(yīng)用程序,歡迎加入社群一起交流討論!
# 界面/圖表報(bào)表/文檔/IDE等千款熱門軟控件火熱銷售中 >>
相關(guān)鏈接:
在上文中(點(diǎn)擊這里查看),我們展示了如何在Visual Studio中針對(duì)Windows和嵌入式Linux創(chuàng)建多平臺(tái)Qt Quick應(yīng)用程序項(xiàng)目。現(xiàn)在,我們將演示如何在嵌入式設(shè)備上運(yùn)行該應(yīng)用程序。然后,我們將繼續(xù)開發(fā)該項(xiàng)目,實(shí)現(xiàn)預(yù)定的完整嵌入式應(yīng)用程序。最后,我們將使用VS調(diào)試器對(duì)應(yīng)用的C ++和QML代碼進(jìn)行遠(yuǎn)程調(diào)試。
Qt技術(shù)交流群:166830288 歡迎一起進(jìn)群討論
我們已經(jīng)演示了如何交叉編譯在Visual Studio中創(chuàng)建的“hello world” Qt Quick應(yīng)用程序。現(xiàn)在,我們將看到如何在樹莓派上運(yùn)行該應(yīng)用程序。由于我們將以全屏模式運(yùn)行,因此必須首先在應(yīng)用程序窗口中添加一些內(nèi)容。
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項(xiàng)目配置,然后按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 ==========
VS中交叉編譯Qt項(xiàng)目
現(xiàn)在我們把應(yīng)用程序的二進(jìn)制文件上傳到樹莓派。構(gòu)建輸出窗口顯示了生成的二進(jìn)制文件的位置(倒數(shù)第二行)。
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>
將應(yīng)用程序二進(jìn)制文件上傳到目標(biāo)設(shè)備
要在為了在每次構(gòu)建結(jié)束時(shí)自動(dòng)復(fù)制應(yīng)用程序文件,可以在“ WSL Post-Build Event”屬性頁中設(shè)置如下命令(注意: 這將以明文形式保存設(shè)備密碼)。
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)
在每次構(gòu)建結(jié)束時(shí)將二進(jìn)制文件復(fù)制到設(shè)備端
在啟動(dòng)Qt Quick應(yīng)用程序之前,我們需要設(shè)置一些必需的環(huán)境變量:
樹莓派命令外殼
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
樹莓派顯示器
在樹莓派上運(yùn)行“ Hello World”應(yīng)用程序
應(yīng)用程序的要求包括顯示以下信息:
我們將把每個(gè)功能項(xiàng)封裝為獨(dú)立的類型。為此,我們必須首先將QML模塊定義(qmldir)文件添加到項(xiàng)目中:
向項(xiàng)目添加新的QML模塊定義
按下“Add”后,qmldir 文件將在項(xiàng)目樹中變?yōu)榭捎谩N覀儗⑹褂么宋募碛成涿糠NQML類型到其對(duì)應(yīng)的源文件。
將QML類型映射到源文件
將新的QML源文件添加到項(xiàng)目中:
向項(xiàng)目添加新的QML文件
我們將首先添加用于顯示當(dāng)前時(shí)間、當(dāng)前日期和重要公共紀(jì)念日的QML類型。該Clock類型將顯示當(dāng)前時(shí)間,每秒刷新一次。
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類型將顯示當(dāng)前日期,并在不同城市名之間循環(huán)。
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類型的定義
除了日期/時(shí)間,我們的應(yīng)用程序還將依靠Web API來檢索信息。我們將在一個(gè)單獨(dú)的進(jìn)程中運(yùn)行curl以連接到Web API。進(jìn)程創(chuàng)建由名為Process的C ++類處理。然后,QML類型ApiCall將通過一個(gè)Process對(duì)象傳送必要的參數(shù)運(yùn)行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類型的定義
創(chuàng)建Process的C ++類:
向項(xiàng)目添加新的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的實(shí)例來獲取重要公共紀(jì)念日列表,并每隔幾秒鐘循環(huán)一次。
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: "http://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" } }
現(xiàn)在,我們已經(jīng)定義了一些應(yīng)用程序的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文件必須添加到應(yīng)用程序的資源文件中:
QML文件和qmldir已添加到資源文件
構(gòu)建和部署后,我們將能啟動(dòng)應(yīng)用程序并查看顯示的信息。
在樹莓派上運(yùn)行的應(yīng)用程序
VS支持通過gdb調(diào)試運(yùn)行在WSL上的應(yīng)用程序。要在樹莓派上運(yùn)行過程中調(diào)試,我們將使用gdbserver啟動(dòng)應(yīng)用程序,然后配置gdb連接到設(shè)備并啟動(dòng)遠(yuǎn)程調(diào)試會(huì)話。
使用gdb 和gdbserver從Visual Studio進(jìn)行遠(yuǎn)程調(diào)試
為此, WSL中安裝的gdb組件必須支持目標(biāo)設(shè)備體系架構(gòu)。一個(gè)簡單的方法是安裝gdb-multiarch。為了確保VS使用正確的調(diào)試器,我們將創(chuàng)建一個(gè)符號(hào)鏈接,把gdb映射到gdb-multiarch。我們將創(chuàng)建從gdb到gdb-multiarch的符號(hào)鏈接。
在Visual Studio中設(shè)置遠(yuǎn)程調(diào)試會(huì)話,必須向gdb傳遞兩個(gè)附加命令。在“GDB Debugger”屬性頁面中進(jìn)行配置。
Project Properties > Debugging > Additional Debugger Commands
target extended-remote 192.168.1.98:2345
set remote exec-file /home/pi/QuickMirror.out
在開始遠(yuǎn)程調(diào)試會(huì)話之前,我們必須設(shè)置所需的環(huán)境變量并在設(shè)備上啟動(dòng)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將啟動(dòng)遠(yuǎn)程調(diào)試會(huì)話。
在嵌入式設(shè)備上運(yùn)行應(yīng)用程序時(shí),也可以。
Project Properties > Debugging > Program Arguments
-qmljsdebugger=port:8989,host:192.168.1.98,block
我們展示了如何使用Qt VS Tools擴(kuò)展在Visual Studio中創(chuàng)建基于Qt Quick技術(shù)的多平臺(tái)嵌入式應(yīng)用程序。包括:
該項(xiàng)目,包括所有源代碼,可在獲得。
運(yùn)行在嵌入式設(shè)備上的應(yīng)用程序
本文轉(zhuǎn)載自
Qt技術(shù)交流群:166830288 歡迎一起進(jìn)群討論
本站文章除注明轉(zhuǎn)載外,均為本站原創(chuàng)或翻譯。歡迎任何形式的轉(zhuǎn)載,但請(qǐng)務(wù)必注明出處、不得修改原文相關(guān)鏈接,如果存在內(nèi)容上的異議請(qǐng)郵件反饋至chenjj@fc6vip.cn
文章轉(zhuǎn)載自: