QGC地面站 UI 界面开发

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/QGCPluginHostQGCMapRCToParamDialog 等,不参与主流程。

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 }
  1. 点击顶部工具栏左侧的 QGC Logo 图标-
  2. 进入 Application Settings(应用设置)
  3. 左侧默认选中 General(常规)
  4. 在右侧找到 Color Scheme(配色方案) 下拉框
  5. 选择:
    • Outdoor → 对应 Light(户外亮色)
    • Indoor → 对应 Dark(室内暗色)

4.1.6 控件库规范(QGC Controls)

QGC 不直接使用 Qt 原生 ButtonTextField,而是封装为带主题的标准控件:

原生控件 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/GradientStopOpacityMask(QtGraphicalEffects)、SVG 图片资源。

③ QGCPitchIndicator --- 俯仰刻度尺,随 roll/pitch 联动旋转。

数据来源:vehicle.roll.rawValuevehicle.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.svgcompassInstrumentArrow.svg)叠加多层指示:

  • 罗盘底盘(随 heading 旋转或 nose-up 锁定)
  • 航向箭头
  • 返航方向指示
  • 下一航点方向指示
  • 地速方向(COG)

设置项 lockNoseUpCompassshowAdditionalIndicatorsCompass 控制行为,来自 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
    }
}

大值区显示高度类属性(altitudeRelativealtitudeAMSL 等),小值区以 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
        }
    }
}

定制方式:

  1. 用户自定义 :点击设置图标 → propertyPicker 对话框选择要显示的 Fact
  2. 插件默认QGCCorePlugin::valuesWidgetDefaultSettings() 覆盖默认大/小值列表
  3. 完全替换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.cppUrlFactory 注册所有地图提供商:

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 插件负责:

  1. 根据当前视口计算需要的瓦片坐标
  2. 先查本地缓存(QGCMapEngine),未命中则 HTTP 下载
  3. 将瓦片图像交给 Qt Location 渲染

地图类型切换在 FlightMap.qmlupdateActiveMapType() 中处理,读取 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,停止自动跟踪
  • 缩放/中心持久化onZoomLevelChangedQGroundControl.flightMapZoom = zoomLevel

Fly View 使用 FlightDisplayViewMap.qml(继承 FlightMap),Plan View 使用内嵌 FlightMap { id: editorMap; planView: true }

4.3.4 航线航点绘制

Plan View 中的航点绘制涉及 Model-View 分离

数据层: MissionController(C++)维护 visualItemsQmlObjectListModel),每个元素是 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 --- 动态生成 Delegate
  • QtPositioning.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 还支持:

  • GeoFenceGeoFenceMapVisuals.qml 绘制多边形/圆形围栏
  • Survey/Corridor/Structure ScanComplexMissionItem 在地图上渲染测区多边形和 transect 线
  • Rally Points:集结点标记

这些均遵循相同模式:Controller 提供 ModelQML 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, ...)
        }
    }
}

参数修改流程:

  1. 用户在 FactTextField 输入新值
  2. fact.validate() 检查范围/类型
  3. fact.value = text 触发 ParameterManager 发送 PARAM_SET
  4. 收到 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 示例
单例 全局服务访问 QGroundControlScreenTools
观察者 数据驱动 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

相关推荐
2603_9547083117 小时前
协调控制柜在微电网中的核心地位:数据枢纽、控制核心、安全屏障
分布式·安全·架构·能源·需求分析
码农天天17 小时前
从SEO到GEO的技术跃迁:如何利用本地化RAG架构解决企业私域数据的“幻觉”难题?
架构
布列瑟农的星空17 小时前
前端架构反思:当MVC失效,是否要采用DDD
架构
高二的笔记17 小时前
Qt翻译时自己写ts文件
qt·国际化翻译
Lhan.zzZ17 小时前
使用 ctx.lineDash 根治 QML Canvas 虚线残留问题(支持 Qt 5.12/5.14 等版本)
开发语言·qt
AI服务老曹17 小时前
深度解析企业级高并发视频云架构:基于 Docker 与 GB28181/RTSP 协议栈的边缘计算平台(附源码交付实践)
docker·架构·音视频
张忠琳17 小时前
【vllm】(v1 Attention)vLLM V1 Attention— Part2 标准Attention后端实现
ai·架构·vllm
跨境数据猎手18 小时前
跨境电商系统开源PHP多语言架构拆解(上)
架构·开源·php
Benszen18 小时前
docker架构
docker·容器·架构