QGC地面站 UI 界面开发
前言
QGroundControl 4.0 的 UI 层以 Qt Quick / QML 为主,辅以少量遗留 Qt Widget (主要用于单元测试和个别对话框)。整体采用 C++ 后端 + QML 前端 的 MVVM 风格架构:C++ 提供数据模型(Fact、Controller),QML 负责布局与交互,通过 Q_PROPERTY 和信号槽实现双向绑定。
4.1 Qt Widget / QML 界面开发规范
4.1.1 技术栈选型与分层
| 层次 | 技术 | 目录 | 职责 |
|---|---|---|---|
| 应用壳层 | QML ApplicationWindow |
src/ui/MainRootWindow.qml |
主窗口、视图切换、全局弹窗 |
| 业务视图 | QML Item/Rectangle | FlightDisplay/、PlanView/、AutoPilotPlugins/ |
Fly/Plan/Setup 各功能页 |
| 控件库 | QML 组件 | src/QmlControls/ |
按钮、对话框、参数编辑等可复用控件 |
| 地图组件 | Qt Location QML | src/FlightMap/ |
地图、航点、仪表盘 |
| 后端桥接 | C++ QGCTool / Controller | Vehicle/、MissionManager/、QmlControls/*Controller |
业务逻辑与数据 |
| 插件扩展 | QGCCorePlugin | src/api/ |
OEM 定制入口 |
QGC 4.0 主界面 几乎完全 QML 化 。MainRootWindow.qml 作为唯一顶层窗口,通过 Loader 按需加载 Fly View、Plan View、Setup View:
qml
property var activeVehicle: QGroundControl.multiVehicleManager.activeVehicle
property bool communicationLost: activeVehicle ? activeVehicle.connectionLost : false
遗留 Widget 仅见于 src/ui/QGCPluginHost、QGCMapRCToParamDialog 等,不参与主流程。
4.1.2 QML 模块组织(qmldir)
QGC 将 QML 组件按功能注册为 Qt Quick 模块 ,通过 import 引用:
QGroundControl 1.0 ← 全局单例 QGroundControl
QGroundControl.Controls 1.0 ← QGCButton、ParameterEditor 等
QGroundControl.ScreenTools 1.0 ← ScreenTools 单例
QGroundControl.Palette 1.0 ← QGCPalette 主题
QGroundControl.FlightMap 1.0 ← 地图与仪表组件
QGroundControl.FlightDisplay 1.0 ← 飞行显示页
QGroundControl.FactSystem 1.0 ← Fact 数据绑定
QGroundControl.FactControls 1.0 ← FactTextField 等
QGroundControl.Controllers 1.0 ← C++ Controller
QGroundControl.Vehicle 1.0 ← Vehicle 类型
Controls/qmldir 片段示例:
QGCButton 1.0 QGCButton.qml
ParameterEditor 1.0 ParameterEditor.qml
MissionItemMapVisual 1.0 MissionItemMapVisual.qml
HeightIndicator 1.0 HeightIndicator.qml
设计模式:模块注册(Module Registration) --- 将上百个 QML 文件组织为命名空间,避免全局命名冲突。
4.1.3 C++ 类型注册规范
QGCApplication::_initCommon() 中集中注册 C++ 类型到 QML 引擎,分三类:
① 可创建类型(qmlRegisterType) --- Controller 和少量工具类:
cpp
qmlRegisterType<ParameterEditorController>(kQGCControllers, 1, 0, "ParameterEditorController");
qmlRegisterType<PlanMasterController>(kQGCControllers, 1, 0, "PlanMasterController");
qmlRegisterType<ValuesWidgetController>(kQGCControllers, 1, 0, "ValuesWidgetController");
② 不可创建类型(qmlRegisterUncreatableType) --- 生命周期由 C++ 管理的对象:
cpp
qmlRegisterUncreatableType<Vehicle>(kQGCVehicle, 1, 0, "Vehicle", "Reference only");
qmlRegisterUncreatableType<MissionController>(kQGCControllers, 1, 0, "MissionController", "Reference only");
③ 单例(qmlRegisterSingletonType) --- 全局访问点:
cpp
qmlRegisterSingletonType<QGroundControlQmlGlobal>("QGroundControl", 1, 0, "QGroundControl", qgroundcontrolQmlGlobalSingletonFactory);
qmlRegisterSingletonType<ScreenToolsController>("QGroundControl.ScreenToolsController", 1, 0, "ScreenToolsController", screenToolsControllerSingletonFactory);
QML 中使用:
qml
import QGroundControl 1.0
QGroundControl.multiVehicleManager.activeVehicle
QGroundControl.settingsManager.appSettings
设计模式:单例(Singleton)+ 外观(Facade) --- QGroundControl 单例封装整个 Toolbox,QML 无需感知底层模块结构。
4.1.4 ScreenTools:屏幕适配规范
QGC 禁止在 QML 中使用固定像素值,统一通过 ScreenTools 单例做分辨率无关布局:
qml
import QGroundControl.ScreenTools 1.0
anchors.margins: ScreenTools.defaultFontPixelWidth
height: ScreenTools.implicitButtonHeight
font.pointSize: ScreenTools.mediumFontPointSize
核心属性:
| 属性 | 用途 |
|---|---|
defaultFontPixelWidth/Height |
以默认字体度量的基本间距单位 |
small/medium/largeFontPointSize |
四级字号体系 |
isMobile/isTinyScreen/isHugeScreen |
平台与屏幕尺寸判断 |
minTouchPixels |
移动端最小触控区域 |
implicitButtonWidth/Height |
标准控件尺寸 |
ScreenTools 在启动时根据 QGCApplication 字体和 DPI 计算基准值,并监听 appFontPointSize 设置变化动态调整。
设计模式:策略(Strategy) --- 不同屏幕尺寸通过属性比率(如 _sizeRatio)缩放控件,而非硬编码。
4.1.5 QGCPalette:主题与配色规范
每个 QML 页面必须声明 QGCPalette 实例并按主题取色:
qml
import QGroundControl.Palette 1.0
QGCPalette { id: qgcPal; colorGroupEnabled: enabled }
Rectangle {
color: qgcPal.window
border.color: qgcPal.buttonText
}
QGC 提供 Light(户外) 和 Dark(室内) 两套主题,每套含 Enabled/Disabled 四组颜色。C++ 侧通过宏批量声明:
cpp
DEFINE_QGC_COLOR(window, ...)
DEFINE_QGC_COLOR(button, ...)
DEFINE_QGC_COLOR(text, ...)
OEM 定制可通过 QGCCorePlugin::paletteOverride() 覆盖特定颜色。
地图控件额外使用 QGCMapPalette,根据卫星/矢量底图切换文字颜色:
qml
QGCMapPalette { id: mapPal; lightColors: editorMap.isSatelliteMap }
- 点击顶部工具栏左侧的 QGC Logo 图标-
- 进入 Application Settings(应用设置)
- 左侧默认选中 General(常规)
- 在右侧找到 Color Scheme(配色方案) 下拉框
- 选择:
- Outdoor → 对应 Light(户外亮色)
- Indoor → 对应 Dark(室内暗色)
4.1.6 控件库规范(QGC Controls)
QGC 不直接使用 Qt 原生 Button、TextField,而是封装为带主题的标准控件:
| 原生控件 | QGC 封装 | 特点 |
|---|---|---|
Button |
QGCButton |
集成 QGCPalette、ScreenTools 尺寸 |
TextField |
QGCTextField |
统一边框、单位标签 |
CheckBox |
QGCCheckBox |
主题色勾选框 |
ComboBox |
QGCComboBox |
下拉选择 |
Menu |
QGCMenu / QGCMenuItem |
上下文菜单 |
Dialog |
QGCViewDialog |
模态对话框基类 |
QGCButton 示例 --- 继承 Qt Quick Controls 1.x Button 并重写 ButtonStyle:
qml
Button {
property bool primary: false
property var _qgcPal: QGCPalette { colorGroupEnabled: enabled }
style: ButtonStyle {
background: Rectangle {
color: _showHighlight ? control._qgcPal.buttonHighlight
: (primary ? control._qgcPal.primaryButton : control._qgcPal.button)
}
}
}
设计模式:装饰器(Decorator) --- 在 Qt 原生控件外包装 QGC 主题和行为。
4.1.7 数据绑定规范:Fact 系统
QGC 的 UI 数据绑定围绕 Fact 对象展开。每个 Fact 是一个带元数据的 QObject:
cpp
class Fact : public QObject {
Q_PROPERTY(QVariant value READ cookedValue WRITE setCookedValue NOTIFY valueChanged)
Q_PROPERTY(QString units READ cookedUnits CONSTANT)
Q_PROPERTY(QString shortDescription READ shortDescription CONSTANT)
};
QML 绑定 Vehicle 遥测:
qml
property real _rollAngle: vehicle ? vehicle.roll.rawValue : 0
property real _pitchAngle: vehicle ? vehicle.pitch.rawValue : 0
property real _heading: vehicle ? vehicle.heading.rawValue : 0
参数编辑使用 FactControls 模块的专用控件:
qml
import QGroundControl.FactControls 1.0
FactTextField { fact: someParameterFact }
FactComboBox { fact: someEnumFact }
FactCheckBox { fact: someBoolFact }
FactSliderPanel { factGroup: someFactGroup }
设计模式:观察者(Observer) --- Fact 的 valueChanged 信号驱动 QML 属性自动刷新,等价于 MVVM 中的 ViewModel。
4.1.8 视图切换与 Loader 模式
MainRootWindow.qml 管理三个主视图,通过 Loader 懒加载:
qml
Loader { id: planViewLoader; source: "PlanView.qml"; visible: false }
Loader { id: setupViewLoader; ... }
// Fly View 直接嵌入或通过 Loader
视图切换函数 viewSwitch(toPlan) 控制 Loader 的 visible 和工具栏状态,并用 pushPreventViewSwitch() 防止切换中弹出对话框导致状态混乱。
设计模式:懒加载(Lazy Loading) --- 非当前视图不渲染,降低内存和 GPU 开销。
4.1.9 C++ Controller + QML View 模式
复杂 UI 逻辑由 C++ Controller 承担,QML 只做展示:
ParameterEditor.qml ←→ ParameterEditorController (C++)
PlanView.qml ←→ PlanMasterController / MissionController (C++)
ValuePageWidget.qml ←→ ValuesWidgetController (C++)
Controller 在 QML 中声明为子对象:
qml
ParameterEditorController {
id: controller
onShowErrorMessage: mainWindow.showMessageDialog(qsTr("Parameter Load Errors"), errorMsg)
}
设计模式:MVC / MVVM --- Model(Fact/Vehicle) + View(QML) + Controller(C++)。
4.2 飞行仪表盘、姿态球、高度速度面板定制

4.2.1 飞行显示总体布局
Fly View 由 FlightDisplayView.qml 组织,核心结构:
FlightDisplayView.qml
FlightDisplayViewMap.qml
FlightDisplayViewVideo.qml
FlightDisplayViewWidgets.qml
GuidedActionsController
GuidedAltitudeSlider
QGCInstrumentWidget
InstrumentSwipeView / ValuePageWidget
支持 地图全屏 / 视频全屏 / PiP 画中画 三种布局,通过 State 切换:
qml
property bool mainIsMap: QGroundControl.videoManager.hasVideo ? ... : true
function setStates() {
if (mainIsMap) {
_flightMapContainer.state = "fullMode"
_flightVideo.state = "pipMode"
} else {
_flightMapContainer.state = "pipMode"
_flightVideo.state = "fullMode"
}
}
4.2.2 姿态球(Artificial Horizon)
姿态球由三层 QML 组件叠加实现:
① QGCArtitudeWidget --- 姿态球容器
qml
Item {
property var vehicle: null
property real _rollAngle: vehicle ? vehicle.roll.rawValue : 0
property real _pitchAngle: vehicle ? vehicle.pitch.rawValue : 0
QGCArtificialHorizon { rollAngle: _rollAngle; pitchAngle: _pitchAngle; anchors.fill: parent }
Image { source: "/qmlimages/attitudePointer.svg"; anchors.fill: parent } // 固定指针
Image {
source: "/qmlimages/attitudeDial.svg"
transform: Rotation { angle: -_rollAngle; origin.x: width/2; origin.y: height/2 }
}
QGCPitchIndicator { pitchAngle: _pitchAngle; rollAngle: _rollAngle; visible: showPitch }
Image { source: "/qmlimages/crossHair.svg"; anchors.centerIn: parent }
}
② QGCArtificialHorizon --- 天地线渲染
qml
Item {
property real rollAngle
property real pitchAngle
property real angularScale: pitchAngle * root.height / 45
Item {
id: artificialHorizon
width: root.width * 4; height: root.height * 8
anchors.centerIn: parent
Rectangle { id: sky; gradient: Gradient { ... } } // 天空(蓝色渐变)
Rectangle { id: ground; height: sky.height/2; anchors.bottom: sky.bottom; gradient: ... } // 地面(绿色渐变)
transform: [
Translate { y: angularScale },
Rotation { angle: -rollAngle; origin.x: width/2; origin.y: height/2 }
]
}
}
实现原理:
- 创建一个 4×8 倍大小的天地矩形
- 通过
Translate.y模拟俯仰(每 45° 对应一个控件高度) - 通过
Rotation模拟横滚 - 外层
clip: true+OpacityMask裁切为圆形
涉及 QML 语法: Transform 列表、Gradient/GradientStop、OpacityMask(QtGraphicalEffects)、SVG 图片资源。
③ QGCPitchIndicator --- 俯仰刻度尺,随 roll/pitch 联动旋转。
数据来源:vehicle.roll.rawValue 和 vehicle.pitch.rawValue 是 Vehicle 上 Fact 对象的原始值,由 MAVLink ATTITUDE 消息更新,QML 属性绑定自动刷新 UI。
4.2.3 罗盘组件(QGCCompassWidget)
qml
Item {
property var vehicle: null
property real _heading: vehicle ? vehicle.heading.rawValue : 0
property real _headingToHome: vehicle ? vehicle.headingToHome.rawValue : 0
property real _headingToNextWP: vehicle ? vehicle.headingToNextWP.rawValue : 0
property real _courseOverGround: activeVehicle ? activeVehicle.gps.courseOverGround.rawValue : 0
罗盘使用 SVG 资源(compassInstrumentDial.svg、compassInstrumentArrow.svg)叠加多层指示:
- 罗盘底盘(随 heading 旋转或 nose-up 锁定)
- 航向箭头
- 返航方向指示
- 下一航点方向指示
- 地速方向(COG)
设置项 lockNoseUpCompass 和 showAdditionalIndicatorsCompass 控制行为,来自 FlyViewSettings。
4.2.4 仪表面板(QGCInstrumentWidget)
标准仪表面板将 姿态球 + 罗盘 并排放入圆角矩形:
qml
Rectangle {
width: getPreferredInstrumentWidth()
height: _outerRadius * 2
radius: _outerRadius
QGCAttitudeWidget {
size: _innerRadius * 2
vehicle: activeVehicle
anchors.verticalCenter: parent.verticalCenter
}
QGCCompassWidget {
size: _innerRadius * 2
vehicle: activeVehicle
anchors.left: attitude.right
}
// 下方 Values 面板
PageView { id: _valuesWidget; maxHeight: _availableValueHeight }
}
尺寸自适应公式:
qml
property real _defaultSize: ScreenTools.defaultFontPixelHeight * 9
property real _sizeRatio: ScreenTools.isTinyScreen ? (width / _defaultSize) * 0.5 : width / _defaultSize
property real _bigFontSize: ScreenTools.defaultFontPointSize * 2.5 * _sizeRatio
4.2.5 高度/速度数值面板
数值面板采用 InstrumentSwipeView 分页设计,包含 4 页:
| 页 | 组件 | 内容 |
|---|---|---|
| 0 | ValuePageWidget |
高度、速度等大/小数值 |
| 1 | CameraWidget |
相机信息 |
| 2 | VehicleHealthWidget |
传感器健康 |
| 3 | VibrationWidget |
振动数据 |
ValuePageWidget 通过 ValuesWidgetController 管理显示哪些 Fact:
qml
ValuesWidgetController { id: controller }
Repeater {
model: _activeVehicle ? controller.largeValues : 0
Loader {
property Fact fact: _activeVehicle.getFact(modelData.replace("Vehicle.", ""))
sourceComponent: fact ? largeValue : undefined
}
}
大值区显示高度类属性(altitudeRelative、altitudeAMSL 等),小值区以 Flow 布局显示地速、空速、爬升率等。
每个值的渲染 Component:
qml
Component {
id: largeValue
Column {
QGCLabel { text: fact.shortDescription + " (" + fact.units + ")"; horizontalAlignment: Text.AlignHCenter }
QGCLabel {
font.pointSize: ScreenTools.mediumFontPointSize * 1.3
font.family: ScreenTools.demiboldFontFamily
text: fact.enumOrValueString
}
}
}
定制方式:
- 用户自定义 :点击设置图标 →
propertyPicker对话框选择要显示的 Fact - 插件默认 :
QGCCorePlugin::valuesWidgetDefaultSettings()覆盖默认大/小值列表 - 完全替换 :
QGCOptions::instrumentWidget指定自定义 QML 源和位置
FlightDisplayViewWidgets.qml 中的定制逻辑:
qml
function _setInstrumentWidget() {
if (QGroundControl.corePlugin.options.instrumentWidget) {
if (QGroundControl.corePlugin.options.instrumentWidget.source.toString().length) {
instrumentsLoader.source = QGroundControl.corePlugin.options.instrumentWidget.source
// 根据 widgetPosition 设置 anchors state
}
} else {
instrumentsLoader.source = _useAlternateInstrumentPanel
? "qrc:/qml/QGCInstrumentWidgetAlternate.qml"
: "qrc:/qml/QGCInstrumentWidget.qml"
}
}
设计模式:策略(Strategy)+ 模板方法(Template Method) --- 通过 Loader 动态切换仪表组件源。
4.2.6 引导高度滑块(GuidedAltitudeSlider)
Fly View 中的高度调整面板,绑定 Vehicle 实时高度:
qml
property real _vehicleAltitude: activeVehicle ? activeVehicle.altitudeRelative.rawValue : 0
// 三次方滑块映射,低区精细、高区粗调
property real altExp: Math.pow(altSlider.value, 3)
property real newAltitudeMeters: _vehicleAltitude + altLossGain
function getAltitudeChangeValue() {
return altField.newAltitudeMeters - _vehicleAltitude
}
涉及语法: 自定义函数、属性绑定链、QGCSlider 控件。
4.2.7 地图高度指示器(HeightIndicator)
Plan View 中航点之间的相对高度差可视化:
qml
ColumnLayout {
property string heightText: "30 ft"
QGCMapLabel { map: _root.map; text: heightText }
// 上下刻度线
}
根据 QGCMapPalette 自动适配卫星/矢量地图的文字颜色。
4.3 电子地图加载、航线打点绘制
4.3.1 地图引擎架构
QGC 使用 Qt Location 框架,配合自研 QGroundControl 地图插件 (src/QtLocationPlugin/):
qml
Map {
plugin: Plugin { name: "QGroundControl" }
gesture.acceptedGestures: MapGestureArea.PinchGesture | MapGestureArea.PanGesture | MapGestureArea.FlickGesture
}
FlightMap.qml
Qt Location Map
QGC Location Plugin
QGCMapEngine
UrlFactory
多地图源
磁盘缓存
4.3.2 地图源与瓦片加载
QGCMapUrlEngine.cpp 中 UrlFactory 注册所有地图提供商:
cpp
_providersTable["Bing Road"] = new BingRoadMapProvider(this);
_providersTable["Bing Satellite"] = new BingSatelliteMapProvider(this);
_providersTable["Mapbox Streets"] = new MapboxStreetMapProvider(this);
_providersTable["Mapbox Satellite"] = new MapboxSatelliteMapProvider(this);
// Google、Statkart、Eniro、VWorld、Airmap Elevation ...
每个 Provider 实现 getTileURL(x, y, zoom) 返回瓦片 URL。Qt Location 插件负责:
- 根据当前视口计算需要的瓦片坐标
- 先查本地缓存(
QGCMapEngine),未命中则 HTTP 下载 - 将瓦片图像交给 Qt Location 渲染
地图类型切换在 FlightMap.qml 的 updateActiveMapType() 中处理,读取 FlightMapSettings 的用户偏好。
4.3.3 FlightMap 基础组件
src/FlightMap/FlightMap.qml 是所有地图视图的基类:
qml
Map {
id: _map
plugin: Plugin { name: "QGroundControl" }
property string mapName: 'defaultMap'
property bool isSatelliteMap: activeMapType.name.indexOf("Satellite") > -1
property var gcsPosition: QGroundControl.qgcPositionManger.gcsPosition
property bool planView: false
property var activeVehicleCoordinate: activeVehicle ? activeVehicle.coordinate : QtPositioning.coordinate()
readonly property real maxZoomLevel: 20
}
关键行为:
- 自动居中:首次收到 Vehicle/GCS 位置时居中地图
- 用户平移检测 :
onPanFinished/onFlickFinished设置userPanned = true,停止自动跟踪 - 缩放/中心持久化 :
onZoomLevelChanged→QGroundControl.flightMapZoom = zoomLevel
Fly View 使用 FlightDisplayViewMap.qml(继承 FlightMap),Plan View 使用内嵌 FlightMap { id: editorMap; planView: true }。
4.3.4 航线航点绘制
Plan View 中的航点绘制涉及 Model-View 分离:
数据层: MissionController(C++)维护 visualItems(QmlObjectListModel),每个元素是 VisualMissionItem。
视图层: QML Repeater + MapQuickItem Delegate:
qml
// PlanView.qml --- 航点标记
Repeater {
model: _editingLayer == _layerMission ? _missionController.visualItems : undefined
delegate: MissionItemMapVisual {
map: editorMap
onClicked: _missionController.setCurrentPlanViewSeqNum(sequenceNumber, false)
}
}
MissionItemIndicator --- 单个航点的地图标记:
qml
MapQuickItem {
property var missionItem
property int sequenceNumber
coordinate: missionItem.coordinate
sourceItem: MissionItemIndexLabel {
label: missionItem.abbreviation
index: missionItem.sequenceNumber
checked: missionItem.isCurrentItem
onClicked: _item.clicked()
}
}
MissionLineView --- 航点连线:
qml
MissionLineView {
model: _missionController.waypointLines
}
// 内部使用 MapPolyline,path 来自 MissionController.waypointPath
方向箭头:
qml
MapItemView {
model: _missionController.directionArrows
delegate: MapLineArrow {
fromCoord: object.coordinate1
toCoord: object.coordinate2
}
}
Fly View 中 PlanMapItems.qml 类似地渲染已上传任务的航点和连线(只读,不可拖拽)。
涉及 QML 语法:
MapQuickItem--- 在地图上放置任意 QML 控件MapPolyline/MapPolygon/MapCircle--- 矢量覆盖物MapItemView--- 对 Model 中每项创建地图元素Repeater--- 动态生成 DelegateQtPositioning.coordinate()--- 地理坐标类型
4.3.5 地图点击添加航点
Plan View 通过 MouseArea 捕获地图点击,转换为地理坐标后插入任务项:
qml
MouseArea {
anchors.fill: parent
onClicked: {
var coordinate = editorMap.toCoordinate(Qt.point(mouse.x, mouse.y), false)
coordinate.latitude = coordinate.latitude.toFixed(_decimalPlaces)
coordinate.longitude = coordinate.longitude.toFixed(_decimalPlaces)
switch (_editingLayer) {
case _layerMission:
if (_addWaypointOnClick) {
insertSimpleItemAfterCurrent(coordinate)
}
break
case _layerRallyPoints:
if (_addWaypointOnClick) {
_rallyPointController.addPoint(coordinate)
}
break
}
}
}
function insertSimpleItemAfterCurrent(coordinate) {
var nextIndex = _missionController.currentPlanViewVIIndex + 1
_missionController.insertSimpleMissionItem(coordinate, nextIndex, true)
}
_addWaypointOnClick 由左侧 ToolStrip 的 "Add Waypoint" 按钮切换(toggle 模式)。
设计模式:命令(Command) --- 每次点击触发 insertSimpleMissionItem,由 MissionController 修改 Model 并通知 View 刷新。
4.3.6 航点拖拽编辑
MissionItemIndicatorDrag.qml 实现航点拖拽:
qml
Rectangle {
property var mapControl
property var itemIndicator
property var itemCoordinate
Drag.active: itemDrag.drag.active
QGCMouseArea {
drag.target: parent
onClicked: itemDragger.clicked()
onDragActiveChanged: {
if (!dragActive) { itemDragger.dragStop() }
else { itemDragger.dragStart() }
}
}
function liveDrag() {
if (itemDrag.drag.active) {
var point = Qt.point(x + anchorPoint.x, y + anchorPoint.y)
var coordinate = mapControl.toCoordinate(point, false)
itemCoordinate = coordinate // 更新 VisualMissionItem 坐标
}
}
}
涉及语法: Qt Quick Drag 内建拖拽、map.toCoordinate() / map.fromCoordinate() 坐标转换、_preventCoordinateBindingLoop 防循环绑定。
4.3.7 地图 Z-Order 管理
QGC 定义全局 Z 序常量(QGroundControl.zOrderWaypointLines 等),确保图层正确叠加:
底层:地图瓦片
↑ 航线连线 (MapPolyline)
↑ 航点标记 (MapQuickItem)
↑ 车辆图标 (VehicleMapItem)
↑ 拖拽手柄 (MissionItemIndicatorDrag)
↑ UI 控件 (ToolStrip, DropPanel)
4.3.8 地理围栏与复杂任务区域
Plan View 还支持:
- GeoFence :
GeoFenceMapVisuals.qml绘制多边形/圆形围栏 - Survey/Corridor/Structure Scan :
ComplexMissionItem在地图上渲染测区多边形和 transect 线 - Rally Points:集结点标记
这些均遵循相同模式:Controller 提供 Model → QML Repeater/MapItemView 渲染。
4.4 弹窗、参数配置、菜单扩展
4.4.1 弹窗体系
QGC 提供两级弹窗机制:
① 简单消息框(MessageDialog)
qml
function showMessageDialog(title, text) {
simpleMessageDialog.title = title
simpleMessageDialog.text = text
simpleMessageDialog.open()
}
MessageDialog {
id: simpleMessageDialog
standardButtons: StandardButton.Ok
modality: Qt.ApplicationModal
}
用于一行式提示(参数加载错误、通信丢失等)。
② 复杂组件对话框(Drawer + Loader)
qml
function showComponentDialog(component, title, charWidth, buttons) {
mainWindowDialog.width = ScreenTools.defaultFontPixelWidth * charWidth
mainWindowDialog.dialogComponent = component
mainWindowDialog.dialogTitle = title
mainWindowDialog.dialogButtons = buttons
mainWindowDialog.open()
}
Drawer {
id: mainWindowDialog
edge: Qt.RightEdge
Loader { id: dlgLoader; anchors.fill: parent }
}
调用示例:
qml
mainWindow.showComponentDialog(propertyPicker, qsTr("Value Widget Setup"),
mainWindow.showDialogDefaultWidth, StandardButton.Ok)
mainWindow.showComponentDialog(validationErrorDialogComponent, qsTr("Invalid Value"),
mainWindow.showDialogDefaultWidth, StandardButton.Save | StandardButton.Cancel)
设计模式:工厂(Factory)+ 组合(Composite) --- showComponentDialog 接受 Component 对象作为内容,按钮通过位掩码配置。
QGCViewDialogContainer.qml 负责:
- 解析
StandardButton位掩码生成 Ok/Cancel/Save 等按钮 - 将
accept()/reject()信号传递给内容组件 - 处理 Escape/Enter 快捷键
QGCViewDialog.qml 是所有自定义对话框的基类:
qml
Item {
signal hideDialog
function accept() { if (acceptAllowed) { hideDialog() } }
function reject() { if (rejectAllowed) { hideDialog() } }
Keys.onReleased: {
if (event.key === Qt.Key_Escape) reject()
if (event.key === Qt.Key_Return) accept()
}
}
4.4.2 DropPanel 下拉面板
用于工具栏按钮的下拉菜单(如 PreFlight Checklist):
qml
DropPanel {
id: checklistDropPanel
toolStrip: toolStrip
function show(panelEdgeTopPoint, panelEdgeHeight, panelComponent) { ... }
}
DropPanel 计算弹出位置(上/下/左/右),绘制箭头指示器,点击外部自动关闭。
设计模式:弹出面板(Popover) --- 相对于触发按钮定位的浮动面板。
4.4.3 参数配置界面
参数编辑器是 QGC 最复杂的 UI 模块之一,位于 ParameterEditor.qml。
架构:
ParameterEditor.qml (View)
↕
ParameterEditorController (C++ Controller)
↕
ParameterManager (C++ Model, per Vehicle)
↕
Fact 对象 (每个参数一个 Fact)
界面结构:
qml
Item {
ParameterEditorController { id: controller }
// 搜索栏
Row {
QGCTextField { id: searchText; onDisplayTextChanged: controller.searchText = displayText }
QGCButton { text: qsTr("Clear"); onClicked: clearTimer.start() }
QGCCheckBox { text: qsTr("Show modified only"); checked: controller.showModifiedOnly }
}
// 参数分组列表
Repeater {
model: controller.parameterGroups
delegate: ParameterGroup { ... }
}
}
Fact 控件绑定:
qml
FactTextField {
fact: someFact
onEditingFinished: {
var errorString = fact.validate(text, false)
if (errorString === "") {
fact.value = text // 写入飞控
} else {
mainWindow.showComponentDialog(validationErrorDialogComponent, ...)
}
}
}
参数修改流程:
- 用户在
FactTextField输入新值 fact.validate()检查范围/类型fact.value = text触发ParameterManager发送PARAM_SET- 收到
PARAM_VALUE确认后valueChanged刷新 UI
ParameterEditorDialog 提供参数的完整元数据视图(描述、范围、单位、枚举值)。
4.4.4 文件对话框
QGC 封装了跨平台文件选择:
QGCFileDialog--- 通用文件对话框(桌面端)HackFileDialog/HackAndroidFileDialog--- 移动端兼容方案KMLOrSHPFileDialog--- 地理数据导入专用
Plan View 中的任务文件操作:
qml
QGCFileDialog {
id: fileDialog
onAccepted: {
if (fileDialog.selectedNameFilterIndex === 0)
_planMasterController.loadFromFile(fileDialog.fileUrl)
else
_planMasterController.saveToFile(fileDialog.fileUrl)
}
}
4.4.5 菜单与工具栏扩展
QGC 的菜单扩展通过 QGCCorePlugin 插件体系 实现,支持 OEM 在不修改核心代码的情况下定制 UI。
① 设置页扩展
cpp
class QGCCorePlugin : public QGCTool {
Q_PROPERTY(QVariantList settingsPages READ settingsPages NOTIFY settingsPagesChanged)
virtual QVariantList& settingsPages(); // 返回 QGCSettings 列表
};
每个设置页由 QGCSettings(继承 QmlComponentInfo)描述:
cpp
class QmlComponentInfo : public QObject {
Q_PROPERTY(QString title READ title CONSTANT)
Q_PROPERTY(QUrl url READ url CONSTANT) // QML 源
Q_PROPERTY(QUrl icon READ icon CONSTANT) // 图标
};
② 分析页扩展
cpp
Q_PROPERTY(QVariantList analyzePages READ analyzePages)
添加自定义分析工具页(如自定义日志解析器)。
③ 工具栏定制
cpp
class QGCOptions : public QObject {
Q_PROPERTY(QUrl mainToolbarUrl READ mainToolbarUrl CONSTANT)
Q_PROPERTY(QUrl planToolbarUrl READ planToolbarUrl CONSTANT)
Q_PROPERTY(QUrl planToolbarIndicatorsUrl READ planToolbarIndicatorsUrl CONSTANT)
};
默认使用 MainToolBar.qml,OEM 可提供自己的工具栏 QML。
MainToolBar.qml 结构:
qml
Item {
Row {
QGCToolBarButton { id: flyButton; text: qsTr("Fly"); onClicked: mainWindow.viewSwitch(false) }
QGCToolBarButton { id: planButton; text: qsTr("Plan"); onClicked: mainWindow.viewSwitch(true) }
QGCToolBarButton { id: setupButton; ... }
// 指示器区域
Loader { source: QGroundControl.corePlugin.options.mainToolbarUrl }
}
}
④ 仪表面板扩展 --- 见 4.2.5 节 instrumentWidget。
⑤ 地图自定义项
cpp
Q_PROPERTY(QmlObjectListModel* customMapItems READ customMapItems)
允许插件在地图上添加自定义 MapQuickItem。
⑥ QML 拦截
cpp
virtual bool mavlinkMessage(Vehicle* vehicle, LinkInterface* link, mavlink_message_t message);
virtual bool adjustSettingMetaData(const QString& settingsGroup, FactMetaData& metaData);
virtual void paletteOverride(QString colorName, QGCPalette::PaletteColorInfo_t& colorInfo);
4.4.6 ToolStrip 工具条
Plan View 和 Fly View 使用 ToolStrip.qml 作为垂直工具条:
qml
Rectangle {
property alias model: repeater.model
signal clicked(int index, bool checked)
ButtonGroup { id: buttonGroup; buttons: toolStripColumn.children }
Repeater {
id: repeater
delegate: QGCToolBarButton {
onClicked: _root.clicked(index, checked)
}
}
}
Plan View 中 ToolStrip 按钮包括:Add Waypoint、Takeoff、Land、Survey、Center Map 等。Toggle 型按钮(如 Add Waypoint)通过 checked 状态控制 _addWaypointOnClick。
设计模式:组合(Composite) --- ToolStrip 通过 Repeater + model 动态生成按钮列表。
4.4.7 OEM 定制工作流
完整的 OEM 定制路径:
1. 创建 custom/custom.pri
├── 定义 CUSTOMCLASS = MyCorePlugin
└── 定义 CUSTOMHEADER = "MyCorePlugin.h"
2. 继承 QGCCorePlugin
├── override settingsPages() → 添加/替换设置页
├── override analyzePages() → 添加分析工具
├── override options() → 返回自定义 QGCOptions
└── override paletteOverride() → 品牌色
3. 继承 QGCOptions
├── mainToolbarUrl → 自定义工具栏 QML
├── instrumentWidget → 自定义仪表
└── brandImageIndoor/Outdoor → 品牌 Logo
4. 可选:替换 qgroundcontrol.qrc / qgcresources.qrc
4.5 设计模式总结
| 模式 | 应用场景 | QGC 示例 |
|---|---|---|
| 单例 | 全局服务访问 | QGroundControl、ScreenTools |
| 观察者 | 数据驱动 UI | Fact.valueChanged → QML 绑定 |
| MVC/MVVM | 复杂页面 | Controller(C++) + View(QML) + Model(Fact) |
| 策略 | 可替换组件 | instrumentWidget Loader 切换 |
| 工厂 | 动态创建 | showComponentDialog(Component) |
| 装饰器 | 主题封装 | QGCButton 包装 Button |
| 组合 | 工具条/菜单 | ToolStrip Repeater |
| 命令 | 用户操作 | insertSimpleMissionItem(coordinate) |
| 懒加载 | 视图切换 | MainRootWindow Loader |
| 模板方法 | 地图渲染 | FlightMap 基类 + 子类扩展 |
| 外观 | 简化接口 | QGroundControlQmlGlobal 封装 Toolbox |
| 插件 | OEM 扩展 | QGCCorePlugin / QGCOptions |
4.6 涉及的核心 QML 语法速查
| 语法/特性 | 用途 | 示例 |
|---|---|---|
property / readonly property |
声明属性 | property var activeVehicle |
alias |
属性别名 | property alias guidedController |
signal / onSignal |
信号与处理器 | onClicked: insertWaypoint() |
Connections |
跨对象信号连接 | Connections { target: controller; onXChanged: ... } |
Component / Loader |
动态组件 | Loader { sourceComponent: largeValue } |
Repeater / delegate |
模型驱动列表 | Repeater { model: visualItems; delegate: ... } |
State / Transition |
状态机布局 | state: "pipMode" |
Binding |
单向/双向绑定 | coordinate: missionItem.coordinate |
anchors / Layout |
布局 | anchors.fill: parent |
Transform / Rotation |
图形变换 | 姿态球 roll 旋转 |
MapQuickItem |
地图标注 | 航点标记 |
MapPolyline |
地图折线 | 航线连线 |
MouseArea / Drag |
交互 | 地图点击、航点拖拽 |
Drawer / Popup |
弹出层 | 复杂对话框 |
import / qmldir |
模块系统 | import QGroundControl.Controls 1.0 |
qsTr() |
国际化 | qsTr("Add Waypoint") |
pragma Singleton |
QML 单例 | ScreenTools.qml |