- 无人机
- 智能硬件
【免费下载链接】qgroundcontrol
Cross-platform ground control station for drones (Android, iOS, Mac OS, Linux, Windows)
QGroundControl(QGC)是一个跨平台地面站应用(支持 Android、iOS、macOS、Linux、Windows),其用户界面(UI)设计遵循一套独特而统一的模式:UI 页面全部由 QML 编写,并与用 C++ 编写的自定义 "Controller" 进行多次通信。这套模式是 MVC(Model-View-Controller)设计模式的一种经过改良的变体(文档原话为 "somewhat hacked variant of the MVC design pattern")。本文以官方开发者指南《用户界面设计》为核心骨架,结合仓库源码(src/QmlControls、src/PlanView、src/FactSystem等)深入讲解 QML 与 C++ 的三种绑定机制、Controller 的编写方式、配套控件与调色板体系,以及该模式背后的多设备设计取舍。读完本文,你将理解 QGC 中任何一张页面的"视图层 + 控制层"是如何组织与通信的,并能据此上手编写或维护自己的 QGC QML 页面。
QGC UI 的核心模式:QML 视图 + C++ Controller
在 QGC 中,一个 UI 页面通常由两部分构成:
- 视图层(View):用 QML 编写的页面,负责界面布局、交互与视觉呈现;
- 控制层(Controller):用 C++ 编写、通过 Qt 元对象系统暴露给 QML 的类,负责业务逻辑、数据访问与状态维护。
这两部分通过 QML 引擎的上下文属性和类型注册机制相连:Controller 以 QML 元素的形式(如PlanMasterController、FactPanelController)直接实例化在 QML 页面中,页面通过属性绑定(Property Binding)读取其Q_PROPERTY,通过信号(signal)接收状态变化,通过Q_INVOKABLE方法触发动作。这与传统 MVC 中"View 观察 Model、Controller 更新 Model"的严格分层不同——QGC 没有独立的 Model 类,Controller 直接暴露数据属性,因此文档将其称为"改良变体"。
从源码看,这种模式的"改良"体现在:
- Controller 直接作为 QML 元素存在:例如
FactPanelController在 src/FactSystem/FactControls/FactPanelController.h 中声明为QML_ELEMENT,并暴露Vehicle *vehicle属性以及getParameterFact()、parameterExists()等可调用方法; - Controller 负责组装子对象并注入 QML:例如 src/PlanView/PlanView.qml 中,页面通过
planMasterController.missionController、planMasterController.geoFenceController、planMasterController.rallyPointController访问由 C++ 侧创建好的子控制器,再通过_missionController.visualItems把任务项模型暴露给地图与列表视图; - 一个 QML 页面可以挂载多个 Controller:如 src/PlanView/PlanTreeView.qml 中同一列表视图依据当前编辑层(任务/围栏/集结点)绑定不同的 Controller。
按照官方文档,QML 代码正是通过以下三种机制绑定到与系统相关的信息:
- 自定义 Controller(C++ 对象,页面专属的业务逻辑与数据);
- 全局
QGroundControl对象(提供对活动 Vehicle、链路管理、设置系统等全局服务的访问); - FactSystem(事实系统)(提供对参数的访问,某些情况下提供自定义 Facts)。
下文逐一深入。
机制一:自定义 Controller——QML 与 C++ 的通信桥梁
Controller 是 QGC 中"页面级"逻辑的载体。以 Plan 视图为例,src/PlanView/PlanView.qml 中有这样一段:
PlanMasterController { id: planMasterController flyView: false Component.onCompleted: { _planMasterController.start() _missionController.setCurrentPlanViewSeqNum(0, true) } ... }PlanMasterController是一个 C++ 类,通过 Qt QML 类型系统注册为 QML 元素。它负责创建并管理任务控制器(MissionController)、地理围栏控制器(GeoFenceController)与集结点控制器(RallyPointController),并通过Q_PROPERTY把这些子对象暴露给 QML:
property var _missionController: _planMasterController.missionController property var _geoFenceController: _planMasterController.geoFenceController property var _rallyPointController: _planMasterController.rallyPointControllerQML 页面可以直接调用 C++ 方法并接收返回值,例如 PlanView 中保存/上传前的校验:
function checkReadyForSaveUpload(save) { if (readyForSaveState() == VisualMissionItem.NotReadyForSaveData) { waitingOnIncompleteDataMessage(save) return false } else if (readyForSaveState() == VisualMissionItem.NotReadyForSaveTerrain) { waitingOnTerrainDataMessage(save) return false } return true }这里的readyForSaveState()就是由 C++ Controller 提供的Q_INVOKABLE方法;VisualMissionItem.NotReadyForSaveData等枚举值也是从 C++ 侧(Q_ENUM)注册进 QML 的。
FactPanelController:面向"事实"的通用控制器基类
另一类典型的 Controller 是FactPanelController(src/FactSystem/FactControls/FactPanelController.h)。它被声明为QML_ELEMENT,是许多参数/设置面板 C++ 控制器的基类,核心能力包括:
vehicle属性:暴露当前关联的Vehicle对象;getParameterFact(int componentId, const QString &name):按组件 ID 与参数名获取对应的Fact对象;parameterExists():判断参数是否存在;getMissingParameters():查询首次下载时尚未就绪、但现在应该可用的参数。
它配合 QML 侧的信号missingParametersAvailable使用,让页面在参数真正可用后再刷新 UI,避免"参数未就绪"竞态。可以看到,Controller 承担了"把 C++ 世界的数据安全地搬运到 QML 世界"的职责。
通信机制小结:Q_PROPERTY / Q_INVOKABLE / 信号槽
QGC 中 QML 与 C++ 通信遵循标准 Qt 元对象系统约定:
| 机制 | 作用 | 示例 |
|---|---|---|
Q_PROPERTY | 暴露只读/可写属性,支持 QML 属性绑定与NOTIFY信号联动刷新 | Fact.value、FactPanelController.vehicle |
Q_INVOKABLE | 暴露可从 QML 调用的方法,可带参数并返回结果 | PlanMasterController.readyForSaveState()、Fact.validate() |
signal /NOTIFY | C++ 侧状态变化时通知 QML 更新绑定 | Fact.valueChanged、FactPanelController.missingParametersAvailable |
Q_ENUM/QML_NAMED_ELEMENT | 把枚举与类注册进 QML 命名空间 | VisualMissionItem.NotReadyForSaveData、QGroundControl全局对象 |
机制二:全局 QGroundControl 对象——系统级服务的门面
在 QML 中,QGroundControl是一个全局单例对象,提供对整个应用系统级服务的访问。其 C++ 实现为QGroundControlQmlGlobal(src/QmlControls/QGroundControlQmlGlobal.h):
class QGroundControlQmlGlobal : public QObject { Q_OBJECT QML_NAMED_ELEMENT(QGroundControl) QML_SINGLETON ... };QML_NAMED_ELEMENT(QGroundControl)意味着 QML 里直接以QGroundControl作为元素名访问;QML_SINGLETON表示它是全局单例,无需实例化即可直接引用。
该对象暴露的常用属性(均为CONSTANT或带NOTIFY)包括:
linkManager:链路管理器(TCP/UDP/串口等链接);multiVehicleManager:多飞行器管理器,通过它可以访问当前活动 Vehicle;settingsManager:设置管理器,按模块暴露各类appSettings、planViewSettings等;corePlugin:核心插件实例(可被自定义构建替换);globalPalette:全局 QGC 调色板对象(返回始终可用的颜色组);unitsConversion:单位换算工具;qgcVersion/qgcAppDate/qgcDailyBuild:版本与构建信息;zOrderTopMost/zOrderWidgets/zOrderMapItems等:地图分层中不同元素的 Z 序常量;flightMapPosition/flightMapZoom:飞行地图的中心点与缩放级别(可读写、带信号)。
它还提供一批Q_INVOKABLE方法,典型的有:
saveGlobalSetting(key, value)/loadGlobalSetting(key, defaultValue):读写全局设置;showMessageDialog(owner, title, text, buttons, acceptFunction, closeFunction):显示消息对话框,支持回调函数;startPX4MockLink(...)、startAPMArduCopterMockLink(...)等:启动各类模拟链路(Mock Link),用于开发与测试;copyToClipboard(text):复制文本到系统剪贴板。
在真实页面中它的用法非常普遍。例如 src/PlanView/PlanView.qml:
property var _appSettings: QGroundControl.settingsManager.appSettings property var _planViewSettings: QGroundControl.settingsManager.planViewSettings ... onVisibleChanged: { if(visible) { editorMap.zoomLevel = QGroundControl.flightMapZoom editorMap.center = QGroundControl.flightMapPosition } }以及通过QGroundControl.showMessageDialog(_root, qsTr("Unable to %1")...)弹出模态提示(见 src/PlanView/PlanView.qml)。调色板的使用也通过全局对象:QGroundControl.globalPalette.buttonHighlight、QGroundControl.globalPalette.buttonText(见 src/QmlControls/AltFrameDialog.qml)。
从源码结构可以推断:
QGroundControl相当于"门面(Facade)"——把LinkManager、MultiVehicleManager、SettingsManager、VideoManager、ADSBVehicleManager、NTRIPManager等一堆子系统统一收口到单个 QML 可见对象上,避免每个页面各自创建或到处传递单例。
应用层单例:QGCApplication 与 qgcApp()
与 QML 侧的QGroundControl相对应,C++ 侧还有一个全局应用对象QGCApplication。它在 src/QGCApplication.h 中被定义为:
#define qApp (static_cast<QGCApplication*>(QGuiApplication::instance())) #define qgcApp() qAppQGCApplication继承自QGuiApplication,提供reportMissingParameter()、showAppMessage()、msecsSinceBoot()、qmlAppEngine()等应用级能力。C++ 代码中凡需访问应用级状态(例如 src/FactSystem/Fact.cc 中if (qgcApp())的判空用法),都会经由qgcApp()获取。
机制三:FactSystem——参数与自定义事实的数据层
文档明确指出 QML 绑定信息的第三个来源是FactSystem,它提供对参数的访问,在某些情况下提供自定义 Facts。这一体系的完整介绍见 docs/zh/qgc-dev-guide/fact_system.md,其核心概念如下。
Fact:系统中的单个值
Fact是"系统中单个值"的封装(src/FactSystem/Fact.h)。它被声明为QML_ELEMENT,意味着可以直接在 QML 中使用。每个 Fact 暴露大量Q_PROPERTY:
- 值相关:
value(可读写,带NOTIFY valueChanged)、rawValue、valueString、enumOrValueString、valueEqualsDefault; - 元数据相关:
name、label、units、shortDescription、longDescription、category、group; - 取值范围:
min、max、userMin、userMax、increment、decimalPlaces; - 枚举相关:
enumStrings、enumValues、enumIndex; - 行为标记:
readOnly、writeOnly、volatileValue、vehicleRebootRequired、qgcRebootRequired。
同时提供validate(cookedValue, convertOnly)、clamp(cookedValue)、rawToCooked(rawValue)等Q_INVOKABLE方法,用于在 UI 层做输入校验、限幅与单位换算。这正是"Fact 驱动自动 UI 生成"的基础——控件只需绑定 Fact,取值范围、单位、枚举选项、输入校验都由 Fact 与 FactMetaData 自动给出。
FactMetaData:驱动自动 UI 的元数据
与每个 Fact 关联的FactMetaData提供关于 Fact 的详细信息(类型、范围、单位、枚举、默认值、描述等),用于驱动用户界面自动生成和验证。元数据既可以由代码构造,也可以由 JSON 文件提供(如src/Settings、src/MissionManager目录下大量*.FactMetaData.json/*.SettingsGroup.json文件)。
FactGroup 与 Fact Controls
- FactGroup:一组 Fact 的集合,用于组织事实并管理用户自定义事实。飞行器相关事实(含固件插件
factGroups函数返回的组内事实)可通过getFact("factName")或getFact("factGroupName.factName")获取; - Fact Controls(事实控件):QML UI 控件,连接到 Fact 及其 FactMetaData,为用户提供修改/显示 Fact 值的界面,例如
FactSlider、FactTextField、FactComboBox等(见 src/FactSystem/FactControls 目录)。
自定义构建支持
用户定义的事实可以通过覆盖FirmwarePlugin的factGroups函数添加到自定义固件插件类中(返回 FactGroup 名称映射),通过扩展FactGroup类并以其构造函数传入包含必要信息的 JSON 文件来定义事实元数据;还可以通过覆盖FirmwarePlugin的adata(原文如此,应为 metadata 相关函数)更改现有事实的元数据。这一机制让自定义构建(custom build)能够在不改动 QML 的前提下扩充 UI 可展示的参数。
为什么 QGC 的 QML 无法使用 Qt QML Designer
官方文档特别提醒:由于 QGC 使用的 QML 的复杂性,以及它依赖与 C++ 对象通信来驱动 UI,无法使用 Qt 提供的 QML Designer 来编辑 QML。这与前述三种绑定机制直接相关:
- 大量属性来自 C++ 单例与 Controller(如
QGroundControl.globalPalette.buttonHighlight、controller.visualItems),在设计器中这些对象要么不存在、要么处于未初始化状态; - 页面启动即依赖 C++ 侧完成的初始化(如
PlanMasterController.start()),脱离运行环境的静态编辑无法正确呈现; - 动态重排(多设备自适应布局)依赖运行时屏幕尺寸与
ScreenTools,设计器的固定画布无法模拟。
因此 QGC 开发者实际都是直接手写 QML 源码,配合 QGC 自身的单元测试与 QML UI 测试(见 test/QmlUITests 目录)来验证界面行为。
支撑这套模式的配套体系
围绕上述"QML + Controller"核心模式,QGC 还提供了一套统一的配套体系,让所有页面共享一致的外观与交互。
可复用控件集(QGroundControl.Controls)
QGC 提供一组基础控件用于构建界面(详见 docs/zh/qgc-dev-guide/user_interface_design/controls.md)。它们一般是 Qt 原生 QML 控件之上的薄封装,用 QGC 调色板绘制。使用时导入:
import QGroundControl.Controls标准 Qt QML 控件的 QGC 变体(功能与对应 Qt 控件相同,仅绘制上使用 QGC 调色板):
QGCButtonQGCCheckBoxQGCColoredImageQGCComboBoxQGCFlickableQGCLabelQGCMovableItemQGCRadioButtonQGCSliderQGCTextField
QGC 独有控件(用于创建标准 UI 元素):
DropButton——圆形按钮,点击后展开一组选项面板;典型例子是 Plan 视图中的同步(Sync)按钮;ExclusiveGroupItem——支持 QMLExclusiveGroup概念的自定义控件的基项;QGCView——系统中所有顶级视图的基本控件,提供对 FactPanel 的支持,并负责显示QGCViewDialog与QGCViewMessage;QGCViewDialog——从 QGCView 右侧弹出的对话框,可指定接受/拒绝按钮及对话框内容;典型例子是点击某个参数后弹出的值编辑器对话框;QGCViewMessage——QGCViewDialog的简化版本,允许指定按钮与简单文本消息;QGCViewPanel——QGCView内部的主要视图内容;RoundButton——以图片作为内部内容的圆形按钮控件;SetupPage——所有"安装载具组件"页面(Setup 视图)的基本控件,提供标题、说明与组件页面内容区;SelectableControl——允许用户从一组控件中选择要显示的内容;桌面端右键、移动端长按显示选择 UI;典型用法是让用户在多种仪表显示变体之间切换。
这些控件的实现集中在 src/QmlControls 目录(如QGCButton.qml、QGCCheckBox.qml、DropButton.qml、ExclusiveGroupItem.qml、SetupPage.qml、SelectableControl.qml等),它们共同构成"一次编写、处处复用"的 UI 积木。
字体与调色板(Font and Palette)
QGC 拥有一套标准字体与调色板,所有用户界面都应遵守(详见 docs/zh/qgc-dev-guide/user_interface_design/font_palette.md):
- QGCPalette(QGC 调色板):提供浅色(light)与深色(dark)两种变体。浅色适合户外使用,深色适合室内。正常情况下绝不应直接为 UI 指定颜色,而应始终使用调色板中的颜色;否则你创建的界面将无法随浅色/深色样式切换。QML 中通过
QGroundControl.globalPalette获取全局调色板(如 src/QmlControls/AltFrameDialog.qml 中的buttonHighlight、buttonText),或实例化QGCPalette { id: qgcPal; colorGroupEnabled: true }按控件局部使用(如 src/QmlControls/AppSettings.qml); - QGCMapPalette(QGC 地图调色板):用于绘制在地图之上的颜色。由于地图样式不同(尤其是卫星图与街道图),需要不同的颜色才能清晰绘制:卫星图需要更浅的颜色,街道图需要更深的颜色。
QGCMapPalette为此提供一组颜色,并支持在地图上切换浅色/深色; - ScreenTools(屏幕工具):提供可用于指定字体大小的值,同时提供屏幕尺寸信息以及 QGC 是否运行在移动设备上的信息。典型用法如 src/PlanView/PlanView.qml 中
ScreenTools.defaultFontPixelHeight、ScreenTools.defaultFontPixelWidth参与计算边距与面板宽度。
多设备设计模式:一个代码库适配所有屏幕
QGC 设计用于从桌面、笔记本、平板到小型手机屏幕等多种设备(详见 docs/zh/qgc-dev-guide/user_interface_design/multi_device_pattern.md)。其核心思路是用单一 QML 代码库 + 布局重排适配不同外形,而不是为每种屏幕尺寸单独编码。该模式建立在三个支柱之上:
- Qt 布局控件(Layout):QGC 没有针对不同屏幕尺寸的分离 UI 代码,通常依靠 QML 的 Layout 能力重排同一组 UI 代码;某些情况下在小屏幕上隐藏次要细节(简单的可见性切换模式);
- FactSystem:系统内部统一管理所有单个数据,这个数据模型连接到控件,控件自动获得数据驱动的 UI;
- 严重依赖可重用控件:QGC UI 由可复用控件与 UI 元素构建,任何添加到可复用控件的新功能立即可用于整个 UI;这些可复用控件连接 FactSystem 的 Facts,自动提供合适的 UI。
设计取舍的背景:QGC 以"约 1 人开发团队"的高效维护为前提,严格遵循该模式,否则会导致开发变慢、质量下降。这一决策使 QGC 能凭单一代码库发布到多个操作系统与多种外形设备。其优先级顺序是:平板 > 笔记本 > 桌面 > 手机(任何小屏幕)。手机大小的屏幕当前优先级最低,Fly 视图等主动飞行显示投入更多精力,而 Setup、Plan 等视图在小屏幕上"功能可用但体验可能较差"。
该模式也有明确代价(原文列出的缺点):
- UI 最终是桌面/笔记本/平板/手机的混合风格,未必对其中任何一种做到"优化感";
- 离优先级目标越远,混合方法的体验越差,小屏手机在可用性上受影响最大;
- 某些场景下可复用控件集未必提供绝对最佳 UI,但为了不新增维护面仍坚持复用;
- 所有操作系统使用同一套 UI 代码,QGC 不遵循各操作系统自身的 UI 设计准则,有自己的混合视觉风格;例如 Android 上的 QGC 不一定像安卓应用,iPhone 上的 QGC 也不会像大多数 iPhone 应用那样工作——但该风格对各类系统用户都是可理解的。
优点(原文所列):由于使用混合模型与控件集一次完成 UI 编码,设计新功能耗时更短(布局重排在 Qt QML 中非常强大);UI 功能代码跨外形一致,因此可仅在一个平台上做功能测试,布局流通过移动模拟器做视觉检查即可(常见做法:桌面构建调整窗口大小测试重排,通常也覆盖平板尺寸;手机小屏用模拟器直观验证)。
结语:一条贯穿 QGC 开发的主线
从 docs/zh/qgc-dev-guide/user_interface_design/index.md 出发可以看到,QGC 用户界面开发的主线非常清晰:QML 只负责"怎么呈现",C++ Controller 负责"数据与逻辑",二者通过属性绑定、Q_INVOKABLE方法与信号槽通信;QGroundControl全局单例收口所有系统级服务,FactSystem 提供统一的数据模型与自动 UI 能力;可复用控件、调色板与多设备布局规则保证所有页面风格一致、一套代码跑遍所有平台。无论你要为 QGC 新增一个设置页、任务项还是自定义构建的专属界面,遵循"QML 视图 + Controller + 全局对象 + FactSystem"这条模式主线,再配合控件集与调色板约定,就能写出与官方页面一致、可维护、可跨设备的 UI 代码。
- 无人机
- 智能硬件
【免费下载链接】qgroundcontrol
Cross-platform ground control station for drones (Android, iOS, Mac OS, Linux, Windows)
相关推荐
QGroundControl 用户界面设计模式:QML 页面与 C++ Controller 的 MVC 变体架构
QGroundControl 用户界面设计模式:QML 页面与 C++ Controller 的 MVC 变体架构 QGroundControl(QGC)的界面
无人机智能硬件QGroundControl UI 设计架构解析:QML 页面与 C++ Controller 的 MVC 变体及多设备适配实践
QGroundControl UI 设计架构解析:QML 页面与 C++ Controller 的 MVC 变体及多设备适配实践 QGroundControl(
无人机智能硬件QGroundControl 用户界面设计指南:QML + C++ Controller 架构、控件体系与多设备适配模式
QGroundControl 用户界面设计指南:QML + C++ Controller 架构、控件体系与多设备适配模式 QGroundControl(QGC)
无人机智能硬件
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考