乘风破浪,遇见最美Windows 11之现代Windows桌面应用开发 - 用于Visual Studio 2022/2019的QT VS工具
2018年10月08日,Visual Studio中的QML调试
https://www.qt.io/blog/2018/10/08/qml-debugging-visual-studio
Qt Visual Studio工具的下一个版本,即v2.3.0,将允许在Visual Studio中调试QML应用程序。它将有可能在QML文件中设置断点并逐步执行QML代码。在断点模式下,它可以观察变量并改变它们的值,以及评估任意表达式。QML调试会话将与C++调试会话同时运行,因此,在应用程序的同一调试运行期间,可以在C++和QML中设置断点和观察变量。
Qt VS工具的这个新的调试功能集成了QML调试基础设施(QML debugging infrastructure),它是Qt QML模块的一部分,通过一个TCP端口为调试、检查和剖析应用程序提供服务。为了用QML调试基础设施的功能来扩展Visual Studio调试器,我们提供了一个Visual Studio QML调试引擎。这个调试引擎大部分是由Visual Studio调试器(Visual Studio debugger)的Active Debugging 7 (AD7)可扩展框架的接口实现组成的。
如果一个Qt项目包含任何QML资源文件,启动一个调试会话(例如按F5键),除了启动本地应用程序,现在也连接到该应用程序的QML调试基础设施。这可以在Visual Studio调试器的进程窗口中看到:列出了两个进程,一个是对应于为C++调试会话创建的实际物理进程的本地进程,另一个是QML进程,它不对应于机器上运行的任何物理进程,而是代表与本地进程中QML调试运行时的连接。
由于本地进程和QML进程都存在,所以有可能在C++或QML代码中都请求断点。Visual Studio调试器将把请求转发给适当的调试引擎。像往常一样,在QML代码中,一个填满的圆形断点标记表示一个有效的断点;这意味着该文件位置的断点请求已经被发送到QML运行时(QML runtime),并得到确认。
当一个断点被击中时,Visual Studio将显示调用栈的当前状态。与其他调试使用不同语言的应用程序的情况不同(如.NET + Native调试),QML调试引擎不提供真正的混合模式调试。它与本地调试引擎同时运行,从Visual Studio调试器的角度来看,它与本地进程没有关系。这意味着,即使在同一个调试会话中可以同时调试C++和QML,当QML断点被击中时,显示的堆栈将只包括QML函数调用 -- 这些调用的C++上下文将不可用。
与本地调试的情况一样,在断点模式下,可以在当前活动的调用堆栈帧的上下文中查看和修改局部变量的值,以及为任何变量或表达式创建监视。立即窗口也可用于在当前堆栈帧的上下文中评估任何表达式。
把鼠标移到一个QML表达式上,会弹出一个即时观察窗口(或 "DataTip")。该表达式在当前上下文中的值被显示出来,也可以被修改。
对于任何Qt QML应用程序,QML调试是默认启用的。通过打开Qt项目设置对话框,将 "QML调试(QML Debug)"选项设置为"禁用(Disable)",可以禁用QML调试,并恢复到仅有本地调试。在这个对话框中,它也可以改变QML调试运行时使用的端口。
如前所述,Qt VS工具的QML调试功能将在下一个版本中提供,计划于今年晚些时候在Visual Studio Marketplace发布。预览版将很快可以在Qt网站上下载;当它可用时,我们会在这里发布一个快速更新。
2018年11月28日,Qt Visual Studio工具2.3.0版发布
https://www.qt.io/blog/2018/11/28/qt-visual-studio-tools-2-3-0-released
Qt VS工具的新开发版本,2.3.0版,现在可以在Qt下载页面下载。
重要的变化包括:
关于这个版本所包含的细节,请参考变化日志。
这个开发版本不会被发布到Visual Studio Marketplace。我们将继续开发即将发布到VS Marketplace的正式版本,它将包括一些额外的功能和错误修复。
2019年1月21日,Qt Visual Studio工具2.3.1版发布
https://www.qt.io/blog/2019/01/21/qt-visual-studio-tools-2-3-1-released
Qt VS Tools 2.3.1版现在已经发布到Visual Studio Marketplace。
重要的变化包括:
- 在Visual Studio IDE中调试QML程序
- 使用Qt快速编译器对QML文件进行超时空编译(在开发版本v2.3.0中引入)
- 支持Visual Studio 2019(在开发版本v2.3.0中引入)
- 对2.3.0版报告的错误进行了修复
有关该版本所包含的细节,请参考变更日志。
安装包可以直接从Visual Studio内部安装(通过 "工具>扩展和更新... "菜单)。另外,它也可以从这个页面下载。
QML调试(QML Debugging)
与之前的帖子中提到的相反,新的QML调试功能不会在默认情况下被启用。它必须通过打开Qt项目设置并将 "QML调试 "选项设置为 "启用 "来明确启用。
2019年8月19日,Qt Visual Studio工具2.4 RC版发布
https://www.qt.io/blog/2019/08/19/qt-visual-studio-tools-2-4-rc-released
我们已经发布了Qt Visual Studio Tools 2.4 RC(2.4.0版);安装包可以在Qt下载页面找到。这个版本的特点是改进了Qt工具与Visual Studio项目系统的集成,解决了当前集成方法的一些局限性,最明显的是不能为每个项目配置不同的Qt设置,以及不支持从共享属性表导入这些设置。
在Visual Studio项目系统中使用Qt
Visual Studio项目系统被广泛用作VS中C++项目的构建系统。 在这个系统中,MSBuild提供了项目文件格式和构建框架。Qt VS工具利用MSBuild的可扩展性,在VS项目中提供Qt的设计和构建集成--在文章的最后,我们将仔细看看这种集成是如何工作的,以及新版本中的变化。
到目前为止,Qt VS Tools扩展以一种孤立的方式管理自己的项目设置。这种方式阻碍了Qt在Visual Studio中的整合,使其不能完全受益于VS项目和MSBuild的功能。重要的是,Qt的设置不可能根据构建配置的不同而变化(例如,不同的配置有不同的Qt模块列表),包括Qt本身:只能选择一个Qt的版本/构建,并适用于所有的配置,在多平台项目的情况下是一个重大的缺陷。
Qt VS工具的用户报告的另一个重要限制是不支持从共享属性表文件中导入Qt相关设置。这个功能允许VS项目中的设置在一个团队或组织内共享,从而为这些信息提供一个单一的来源。到目前为止,由Qt VS工具管理的设置还不可能做到这一点。
为了克服这些和其他相关的限制,所有的Qt设置--例如Qt的版本、要使用的模块或生成的源的路径--现在将被存储为完全成熟的项目属性。目前的Qt设置对话框将被删除,取而代之的是Qt设置属性页。因此,它将有可能根据配置来设置所有的Qt设置值,以及从属性表文件中导入这些值。
仔细观察(A closer look)
一个过于简化的入门指南可以这样描述MSBuild:
- 一个MSBuild项目由对源文件的引用和为处理这些源文件而采取的行动的描述组成,这些描述被称为目标。
- 构建过程在项目配置的上下文中运行(例如,调试(Debug)、发布(Release)等),一个项目可以包含任何数量的配置。
- 与源文件和项目本身相关的数据可以通过属性访问。MSBuild属性是名-值定义,按配置指定(即每个配置有自己的属性定义集)。
属性可以适用于项目本身或项目中的特定文件,并且可以全局或局部定义:
- 项目范围的属性总是全局的(如项目的输出目录或目标文件名)。
- 适用于源文件的属性可以全局定义,在这种情况下,相同的值将适用于所有文件(例如,默认编译器警告级别被全局定义为3级)。
- 这种全局的、文件范围的定义可以被本地定义的同名属性覆盖(例如,其中一个源文件需要以警告级别4编译)。
- 全局定义存储在项目文件中或从属性表文件中导入。
- 本地属性的定义存储在项目文件中,在相关的源文件引用中。
Qt Visual Studio工具扩展与MSBuild项目系统集成,提供了一组Qt特定的目标,描述了如何用适当的Qt工具处理文件(如moc头文件)。
就MSBuild项目系统的功能而言,目前的集成有一些限制:
- 用户管理的Qt构建设置在改变时被复制到项目属性。鉴于这种单向的同步,项目属性可能会与相应的Qt设置不同步。
- Qt构建设置的值对所有配置都是一样的,例如,无论选择何种配置,都会使用相同的Qt构建和模块。
- 不可能覆盖生成文件中的属性,如moc的元对象源代码输出。
- Qt设置只能存储在项目文件中。因此,不可能从共享的属性表中导入Qt定义,例如在几个项目中共享的共同Qt构建。
如上所述,解决这些限制的方法是使Qt设置成为完全成熟的项目属性。通过这种方式,Qt设置将被保证与项目中的所有其他属性同步,并有可能为每个构建配置定义不同的内容。它将有可能从属性表中导入Qt设置,而生成C++代码的Qt工具的属性页,如moc,现在将允许在生成的文件中覆盖编译器属性。
2019年11月06日,Qt Visual Studio工具2.4.2版发布
https://www.qt.io/blog/qt-visual-studio-tools-2.4.2-released
我们很高兴地宣布Qt Visual Studio Tools 2.4.2版本的发布。
获取Qt VS工具
安装包可以在Visual Studio Marketplace获得。请注意,如果你安装了以前版本的Qt VS Tools,并且VS扩展设置为自动更新--这是默认设置--那么你的安装可能已经是最新的了。你可以通过打开Qt VS Tools菜单来检查当前安装的是哪个版本。
变化
这个版本修复了在v2.4版本中引入新功能后发现的几个问题。请参考变更日志中的修复列表。一些已知的问题仍未解决,将在以后的版本中修复。在此期间,请随时在Qt Visual Studio Tools的bug追踪器中报告任何问题,或提出任何建议或意见。
2020年2月10日,用Qt和Visual Studio进行跨平台开发
https://www.qt.io/blog/cross-platform-development-with-qt-and-visual-studio
在即将发布的Qt Visual Studio工具(计划在今年夏天发布)中,我们计划增加对Visual Studio Linux项目的支持。自从引入C++ Linux工作负载后,用户就有可能在Visual Studio中进行Linux开发。考虑到Qt本身的跨平台特性,这个功能对Qt开发者来说有潜在的兴趣,这就是为什么我们现在计划在Qt VS Tools扩展中加入对它的支持。
至于交叉编译的实际工作方式,当在VS中构建Linux项目时,构建过程和整体协调将依赖于MSBuild或CMake,与传统的Win32项目相同。构建工具本身将在一个编译服务器中运行,可以通过SSH访问。对于MSBuild项目,Visual Studio允许选择gcc或clang作为C++编译器。
我们增加对Linux项目的支持的计划是使用VS Linux开发工作负载中的功能来扩展现有的Qt/MSBuild目标。我们的目标是能够构建和运行支持Qt的MSBuild项目,既可以是Windows的原生项目,也可以是交叉构建的Linux项目。
作为实现这一目标的第一步,并作为一个概念验证,我们手动将一个简单的Qt例子(wiggly)转换为一个MSBuild项目,同时针对Windows和Linux。按下F5将为所选目标构建项目,然后启动调试会话,如果是Windows构建,可以在本地启动,如果是Linux,则通过gdbserver
启动。
这个练习表明,使用Visual Studio IDE来开发、构建甚至调试多平台的Qt应用程序是可能的。因此,无论目标平台如何,Qt VS Tools扩展应该能够无缝地协助MSBuild项目中Qt工具的设置和使用。我们还了解到,对Qt/MSBuild目标的修改,是支持本地和跨平台构建的需要,主要集中在Qt工具的调用方式上。大部分(如果不是全部)支持Qt与MSBuild集成的逻辑将在Linux上保持有效。
在这篇文章中,我们已经涵盖了VS被用来针对Linux主机的情况。然而,考虑到VS的Linux工作负载只需要一个SSH可访问的GNU工具链,因此可以使用一个跨编译器的工具链来代替主机的。可以想象,使用VS作为替代IDE来开发Qt的MCU应该是可能的。我们将在另一篇博文中探讨这种情况。
感谢你的阅读,感谢你对Qt和VS工具扩展的兴趣。如果你认为(或不认为)这个话题是相关的,或者你有任何问题或建议,请在下面留言。
2020年6月29日,Qt Visual Studio工具2.5.2版发布
https://www.qt.io/blog/qt-visual-studio-tools-2.5.2-released
我们很高兴地宣布Qt Visual Studio工具2.5.2版的发布。安装包现在可以在Visual Studio Marketplace上找到。
除了几个重要的错误修复,这个新版本的Qt VS工具允许创建参考多个Qt版本的项目。重新设计的项目创建向导现在可以根据需要定义多个项目配置,并在每个配置中指定使用哪个版本的Qt和Qt模块集。例如,现在可以创建一个同时针对32位和64位Windows的Qt项目。
这一变化代表了我们为支持Visual Studio / MSBuild C++项目的多平台Qt开发而持续努力的第一步。在下一个版本(v2.6)中,我们计划通过与Visual Studio C++ Linux Workload的集成,引入Qt项目的交叉编译。
你可以在bugreports.qt.io中跟踪我们的工作进展。请随时报告任何问题,或提出任何建议/意见,帮助我们进一步改进Qt Visual Studio工具。
2020年9月24日,Qt Visual Studio工具2.6版发布
https://www.qt.io/blog/qt-visual-studio-tools-2.6.0-released
我们很高兴地宣布Qt Visual Studio工具2.6.0版的发布。安装包现在可以在Visual Studio Marketplace获得。
v2.6版通过Linux开发VS工作负载,增加了对Visual Studio/MSBuild C++项目多平台Qt开发的支持。这使得在Visual Studio中使用远程托管的交叉编译器(或在本地WSL实例上)构建Qt项目成为可能。
你可以在bugreports.qt.io跟踪工作进展并提供反馈。请随时报告任何问题,或提出任何建议/意见,帮助我们进一步改进Qt Visual Studio工具。
我们将在不久之后发表一篇内容广泛的文章,详细介绍如何在Visual Studio中开发针对嵌入式Linux设备的多平台Qt Quick应用程序。我们将描述开发过程的所有步骤,从Visual Studio中创建一个新的多平台Qt Quick项目开始,到交叉编译、部署和远程调试。
敬请关注!
2020年9月24日,在Visual Studio中创建一个嵌入式Qt Quick应用程序(1/2)
https://www.qt.io/blog/creating-an-embedded-qt-quick-application-in-visual-studio-1/2
Qt Visual Studio工具现在支持Qt项目的交叉编译。这是由于Qt/MSBuild和Linux开发VS工作负载的整合而实现的。在这篇文章中,我们将展示如何使用这一功能在Visual Studio中用Qt Quick创建一个嵌入式应用程序。
我们将开发一个用于在镜像屏幕上显示公共信息的应用程序。这些是在休息区、洗手间等地方安装的一部分。该应用程序将针对运行嵌入式Linux的设备。在这篇文章中,我们将使用一个运行Raspbian(Buster)的Raspberry Pi 3 Model B。
我们的Qt Quick嵌入式应用程序在目标设备上运行。
准备开发环境
开发主机将是一台Windows 10机器。必须安装Visual Studio 2019,包括桌面开发(Desktop development) 和Linux开发(Linux development) 工作负载。
所需的Visual Studio工作负载
我们将在Windows Subsystem for Linux(WSL)的一个实例中运行交叉编译器。设置开发环境的下一步是启用WSL并安装一个Linux发行版。然后应该可以打开一个终端窗口并运行Linux命令。
WSL Command Shell
user@buildhost:~$ uname -srvo
Linux 4.4.0-18362-Microsoft #836-Microsoft Mon May 05 16:04:00 PST 2020 GNU/Linux
user@buildhost:~$
要使用Qt Visual Studio工具,至少要有一个Qt for Windows的安装。这也将允许在开发机器上运行应用程序,以达到测试目的。
开发环境概述
交叉编译(Cross-compiling)Qt
我们现在可以着手设置交叉编译器。我们可以安装一个预建的工具链,或者编译我们自己的工具链。如果一切顺利的话,我们应该能够在设备上运行一个简单的程序。
WSL Command Shell
user@buildhost:~$ echo "#include <iostream>" > main.cpp
user@buildhost:~$ echo "int main() {" >> main.cpp
user@buildhost:~$ echo "std::cout << \"Hello World from Raspberry Pi\\n\";" >> main.cpp
user@buildhost:~$ echo "return 0;" >> main.cpp
user@buildhost:~$ echo "}" >> main.cpp
user@buildhost:~$ /opt/cross-pi-gcc-8.3.0/bin/arm-linux-gnueabihf-g++ main.cpp
user@buildhost:~$ scp a.out pi@192.168.1.99:/home/pi
pi@192.168.1.99's password:
a.out 100% 12KB 218.1KB/s 00:00
user@buildhost:~$ ssh pi@192.168.1.99 ./a.out
pi@192.168.1.99's password:
Hello World from Raspberry Pi
user@buildhost:~$
在Raspberry Pi上运行 "Hello World"。
我们已经准备好为Raspberry Pi构建Qt了。为此,我们将按照教程"RaspberryPi2EGLFS--在Raspbian上用eglfs交叉编译Qt用于HW加速OpenGL并设置Qt Creator的现代指南"。一旦构建完成,我们应该能够在Raspberry Pi上运行一个Qt应用程序样本。
WSL Command Shell
user@buildhost:~$ echo "#include <QDebug>" > main.cpp
user@buildhost:~$ echo "int main() {" >> main.cpp
user@buildhost:~$ echo "qInfo() << \"Hello World with Qt version\" << qVersion();" >> main.cpp
user@buildhost:~$ echo "return 0;" >> main.cpp
user@buildhost:~$ echo "}" >> main.cpp
user@buildhost:~$ echo "SOURCES += main.cpp" > hello.pro
user@buildhost:~$ raspi/qt5/bin/qmake
user@buildhost:~$ make -s
user@buildhost:~$ scp hello pi@192.168.1.99:/home/pi
pi@192.168.1.99's password:
hello 100% 12KB 359.6KB/s 00:00
user@buildhost:~$ ssh pi@192.168.1.99 ./hello
pi@192.168.1.99's password:
Hello World with Qt version 5.12.9
user@buildhost:~$
在Raspberry Pi上运行一个Qt示例应用程序
设置Qt Visual Studio工具
要从IDE中安装Qt Visual Studio工具(Qt Visual Studio Tools),请打开扩展(Extensions) > 管理扩展(Manage Extensions)。搜索"qt":搜索结果应显示"Qt Visual Studio Tools"扩展;选择它并按"下载"。关闭VS,按照安装说明操作。重新打开VS后,Qt Visual Studio工具应该可用。
Qt Visual Studio Tools菜单
为了使用Qt Visual Studio工具,我们必须首先注册我们将要使用的Qt版本。我们将从注册Windows版的Qt开始。
- 打开 "Qt VS Tools > Qt Options",在 "Qt Versions"标签上按"Add"。
- 提供一个名称和Qt安装的路径。
在Qt VS工具中注册Qt for Windows
我们现在要注册驻扎在WSL实例中的交叉编译的Qt。
- 在 "Qt版本 "标签上再次按 "添加",打开 "添加新的Qt版本 "对话框。
- 将 "构建主机 "设置为 "Linux WSL"(如果我们使用的是物理构建主机而不是通过WSL的虚拟主机,那么我们可以选择 "Linux SSH")。
- 提供Qt安装的名称和路径,以及要使用的编译器(默认为g++)。这里我们将提供交叉编译器的路径。
为Raspberry Pi注册Qt build
创建一个多平台的Qt Quick项目
在注册了我们将要使用的Qt版本后,我们现在可以创建一个Qt项目。
- 选择 "文件">"新建">"项目",打开Visual Studio创建新项目的对话框。
- 在项目模板搜索中输入 "qt "并选择 "Qt Quick Application"。
- 按 "下一步";为项目提供一个名称和位置,然后按 "创建"。
- Qt Quick Application Wizard对话框将打开;按 "Next >"进行配置设置。
- 为Windows和Raspberry Pi版本的Qt添加项目配置。
新项目配置
按 "Finish "来完成项目创建向导。Visual Studio现在应该显示新的多平台Qt Quick项目。选择一个Windows配置,按F5键来构建和运行该应用程序。一个带有 "Hello World "标题的空窗口应该随之打开。
在Visual Studio中用WSL进行交叉编译
使用WSL的一个实例来运行交叉编译器比使用SSH连接到构建主机要快。当使用SSH时,MSBuild必须首先将源文件上传到远程主机。另一方面,WSL会在/mnt
中挂载本地驱动器,使文件的访问变得瞬息万变。在这种情况下,MSBuild会将Windows的路径映射成对应的Linux路径,并将其扎根在/mnt
上,例如。
C:\include\foolib maps to /mnt/c/include/foolib
MSBuild会假设属性页中提供的路径是本地路径,并将它们映射到/mnt
。当在属性页中使用绝对的Linux路径时,这可能导致不正确的映射,例如。
/home/user/raspi maps to /mnt/home/user/raspi
为了避免这个问题,我们将在/mnt创建符号链接到WSL实例文件系统的其他顶级目录。
WSL Command Shell
user@buildhost:/mnt$ sudo ln -s /home home
user@buildhost:/mnt$ ls -go
total 0
drwxrwxrwx 1 4096 Sep 1 15:29 c
drwxrwxrwx 1 4096 Sep 1 15:29 d
drwxrwxrwx 1 4096 Sep 1 15:29 e
lrwxrwxrwx 1 5 Sep 1 17:10 home -> /home
user@buildhost:/mnt$
在/mnt
创建一个符号链接
我们现在准备对项目进行交叉编译。选择一个Linux配置,按F7开始构建。
Visual Studio Build Output
1>------ Rebuild All started: Project: QuickMirror, Configuration: Release_RPi x64 ------
1>Invoking 'mkdir -p $(dirname /mnt/c/Users/user/Source/Repos/QuickMirror/obj/x64/Release_RPi/qmake/temp/props.txt); mkdir -p /mnt/c/Users/user/Source/Repos/QuickMirror/obj/x64/Release_RPi/qmake/temp; cd /mnt/c/Users/user/Source/Repos/QuickMirror/obj/x64/Release_RPi/qmake/temp; ("/home/user/raspi/qt5/bin/qmake" -query) 1> props.txt', working directory: '/mnt/c/Users/user/Source/Repos/QuickMirror'
1>Reading Qt configuration (/home/user/raspi/qt5/bin/qmake)
1>Invoking 'mkdir -p $(dirname /mnt/c/Users/user/Source/Repos/QuickMirror/obj/x64/Release_RPi/qmake/temp/qtvars.pro); mkdir -p $(dirname /mnt/c/Users/user/Source/Repos/QuickMirror/obj/x64/Release_RPi/qmake/temp/Makefile); mkdir -p $(dirname /mnt/c/Users/user/Source/Repos/QuickMirror/obj/x64/Release_RPi/qmake/temp/qtvars.log); mkdir -p /mnt/c/Users/user/Source/Repos/QuickMirror/obj/x64/Release_RPi/qmake/temp; cd /mnt/c/Users/user/Source/Repos/QuickMirror/obj/x64/Release_RPi/qmake/temp; ("/home/user/raspi/qt5/bin/qmake" "CONFIG -= debug release debug_and_release" "CONFIG += release" qtvars.pro) 1> qtvars.log 2>&1', working directory: '/mnt/c/Users/user/Source/Repos/QuickMirror'
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/Release_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/Release_RPi/rcc/qrc_qml.cpp)', working directory: '/mnt/c/Users/user/Source/Repos/QuickMirror'
1>Starting remote build
1>Compiling sources:
1>main.cpp
1>qrc_qml.cpp
1>Linking objects
1>QuickMirror.vcxproj -> C:\Users\user\Source\Repos\QuickMirror\bin\x64\Release_RPi\QuickMirror.out
1>Done building project "QuickMirror.vcxproj".
========== Rebuild All: 1 succeeded, 0 failed, 0 skipped ==========
在VS中交叉编译Qt项目
即将上线
综上所述,我们在Visual Studio中创建了一个多平台的Qt Quick项目,并使用交叉编译器为Raspberry Pi构建了它。在本篇文章的第二部分,我们将看一下。
- 部署和运行该应用程序
- QML中的应用代码
- 在目标设备中进行调试
2020年9月30日,在Visual Studio中创建一个嵌入式Qt Quick应用程序(2/2)
https://www.qt.io/blog/creating-an-embedded-qt-quick-application-in-visual-studio-2/2
在这篇文章的第一部分,我们展示了如何在Visual Studio中创建一个针对Windows和嵌入式Linux的多平台Qt Quick应用程序项目。现在我们将展示如何在嵌入式设备上运行该应用程序。然后,我们将继续将该项目开发成我们所要创建的完整的嵌入式应用程序。最后,我们将使用VS调试器对应用程序的C++和QML代码进行远程调试。
在嵌入式设备上运行
我们已经展示了如何交叉编译一个在Visual Studio中创建的"hello world" Qt Quick应用程序。现在我们将看看如何在Raspberry Pi上运行该应用程序。由于我们将在全屏模式下运行,我们必须首先向应用程序窗口添加一些内容。
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 Build Output
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项目
现在我们将把应用程序的二进制文件上传到Raspberry Pi。构建输出窗口显示了生成的二进制文件的位置(上面突出显示)。
Windows Command Prompt
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构建后事件"属性页中设置以下命令(ATTN:这将以明文形式保存设备密码)。
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应用程序之前,我们需要设置一些必要的环境变量。
- 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模块的路径
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:~$ ./QuickMirror.out
Raspberry Pi Display
在Raspberry Pi中运行 "Hello World"应用程序
开发应用程序
我们的应用程序的要求包括显示以下信息。
- 当前时间
- 当前日期
- 值得注意的周年纪念日
- 天气预报
- 公共交通的下一个出发点
- 新闻
我们将把这些项目中的每一项都封装成一个专门的QML类型。为此,我们必须首先在项目中添加一个QML模块定义(qmldir
)文件。
- 选择 "项目 > 添加新项目... > Qt > QML Module Definition"。
- 在位置栏中,指出将包含QML文件的文件夹的路径。
在项目中添加一个新的QML模块定义
在按下 "添加 "后,一个qmldir文件将在项目树中出现。我们将使用这个文件来定义每个QML类型到其相应的源文件的映射。
qmldir
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类型映射到源文件
要添加一个新的QML源文件到项目中。
- 选择 "Project > Add New Item... > Qt > QML文件"。
- 将位置设置为创建qmldir文件的同一目录。
- 设置QML文件的名称。
- 按 "Add"。
在项目中添加一个新的QML文件
我们将首先添加QML类型来显示当前时间、当前日期和值得注意的纪念日。Clock类型将显示当前时间,每秒钟刷新一次。
QuickMirror.Clock.qml
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();
}
}
时钟QML类型的定义
日历类型将显示当前的日期,在不同的地域之间循环。
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 }
}
}
}
Calendar QML类型的定义
除了日期/时间,我们的应用程序将依靠Web API来检索信息。我们将在一个单独的进程中运行curl来连接到Web API's。进程的创建将由一个名为Process的C++类来处理。然后,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);
}
}
}
}
ApiCall QML类型的定义
要创建Process C++类。
- 选择 "项目>添加Qt类>Qt类"
- 将类的名称设为Process
- 按 "添加"。
在项目中添加一个新的Qt C++类
Process.h
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<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");
...
过程类的定义
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 = "<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"
}
}
OnThisDay QML类型的定义
现在我们已经定义了一些应用程序的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
}
}
}
}
带有时钟、日历和OnThisDay的主QML
最后,QML文件和qmldir文件都必须被添加到应用程序的资源文件中。
- 双击项目树中的QRC文件
- 在Qt资源编辑器窗口中,按 "添加>添加文件"
- 选择所有的QML文件和qmldir文件
- 在Qt资源编辑器中按 "保存 "键
QML文件和qmldir添加到资源文件中
在构建和部署之后,我们就可以启动应用程序并看到显示的信息。
Raspberry Pi Display
在Raspberry Pi上运行的应用程序
在Visual Studio中调试
VS支持通过gdb
调试运行在WSL上的应用程序。为了在Raspberry Pi上运行时进行调试,我们将使用gdbserver
启动应用程序,然后配置gdb
连接到设备并启动远程调试会话。
使用gdb和gdbserver从Visual Studio进行远程调试
要做到这一点,安装在WSL中的gdb
必须支持目标设备的架构。实现这一点的一个简单方法是安装gdb-multiarch
。为了确保VS使用正确的调试器,我们将创建一个从gdb
到gdb-multiarch
的符号链接。
WSL Command Shell
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$
用gdb-multiarch替换gdb
为了在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
用于远程调试会话的附加gdb命令
在开始远程调试会话之前,我们必须设置必要的环境变量并在设备上启动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
在Raspberry Pi上启动gdbserver
按F5将启动远程调试会话。
在远程调试过程中停在C++代码的断点上
远程QML调试
在嵌入式设备上运行应用程序时,也可以对QML代码进行调试。
- 在Qt设置(Qt settings)中启用QML调试(QML debugging)。项目属性(Project Properties) > Qt项目设置(Qt Project Settings)
- 设置启动QML调试会话(QML debug session)的程序参数
Project Properties > Debugging > Program Arguments
-qmljsdebugger=port:8989,host:192.168.1.98,block
启动QML调试会话的程序参数
在远程调试过程中停在QML代码的断点上
总结
我们已经展示了如何使用Qt VS Tools扩展,在Visual Studio中用Qt Quick创建一个多平台的嵌入式应用程序。这包括
- 从头开始创建一个Qt Quick项目
- 用QML编写应用程序的代码
- 交叉编译该应用程序
- 在嵌入式设备上部署和运行
- 在Visual Studio中对C++和QML代码进行远程调试
该项目,包括所有的源代码,可在以下网站获得:https://github.com/micosta/quickmirror。
我们的应用程序在嵌入式设备上运行
感谢你的阅读,感谢你对Qt和VS Tools扩展的兴趣。如果你有任何问题或建议,请在下面留言。
2021年11月15日,用于Visual Studio 2022的QT VS工具
https://marketplace.visualstudio.com/items?itemName=TheQtCompany.QtVisualStudioTools2022
我们很高兴地宣布,Visual Studio 2022的Qt Visual Studio工具(v2.8.0)扩展已经发布。安装包可在VS Marketplace下载,或直接在VS 2022 IDE中下载:在IDE菜单中选择扩展 > 管理扩展,然后搜索"qt"。
Qt VS Tools扩展的2.8.0版本也可作为Visual Studio 2019和2017的开发版本。安装包可以从download.qt.io下载。
你可以在bugreports.qt.io跟踪我们的工作进展并提供反馈。请随时报告任何问题,或提出任何建议/意见,帮助我们进一步改进Qt Visual Studio工具。
参考
- Qt VS Tools for Visual Studio 2022
- Creating an Embedded Qt Quick Application in Visual Studio (1/2)
- Creating an Embedded Qt Quick Application in Visual Studio (2/2)
- Qt Visual Studio Tools 2.6 Released
- Qt Visual Studio Tools 2.5.2 Released
- Qt Visual Studio Tools 2.4.2 Released
- QML Debugging in Visual Studio
- Qt Visual Studio Tools 2.3.0 Released
- Qt Visual Studio Tools 2.3.1 Released
- Qt Visual Studio Tools 2.4 RC Released
- Cross Platform Development with Qt and Visual Studio
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了