3、QGC核心类:QGCApplication、MainWindow、QmlEngine、SettingsManager、LinkManager
QGC 的架构,说白了就是几个核心类撑起来的。你想想看,一个地面站软件要处理的事情太多了——启动、界面、设置、通信链路……如果全写在一个类里,那代码就彻底没法维护了。
我个人习惯,拿到一个开源项目,第一件事就是看它的核心类结构。QGC 在这方面做得挺清晰,五个类各司其职。今天我们就一个一个拆开来看。
3.1 QGCApplication:整个应用的“大脑”
QGCApplication 继承自 QApplication,是整个 QGC 的入口。它负责应用的初始化、生命周期管理,以及一些全局状态的控制。
我记得第一次看 QGC 的 main.cpp,发现它并没有直接创建 QApplication,而是创建了 QGCApplication。这个设计其实很巧妙——把启动逻辑封装在子类里,main 函数就变得非常干净。
核心职责:
- 应用初始化(日志、翻译、命令行参数)
- 全局单例管理(通过
_app静态指针) - 应用退出流程控制
- 跨平台适配(移动端 vs 桌面端)
来看一段典型的初始化代码:
// main.cpp 中的典型用法
QGCApplication* app = new QGCApplication(argc, argv);
if (!app->_initForNormalAppStart()) {
return -1;
}
return app->exec();
这里有个坑,我曾经踩过。如果你在插件或自定义模块里需要访问 QGC 的全局状态,千万别直接 new QGCApplication,要用 qgcApp() 这个宏。它内部做了空指针检查,安全得多。
避坑指南:我曾经在自定义飞行日志模块里直接引用了 QGCApplication::_app,结果在单元测试环境下 _app 是 nullptr,直接崩溃。后来全部改用 qgcApp() 宏,问题解决。
3.2 MainWindow:界面的大管家
MainWindow 是 QGC 的主窗口,但它不是传统意义上的“窗口”。它更像一个容器,负责管理各种 QML 页面的切换和布局。
你想想看,QGC 的界面从“飞行视图”切换到“规划视图”,再到“设置视图”,这些切换逻辑全在 MainWindow 里。它内部维护了一个 QStackedWidget,每个页面就是一个独立的 QML 组件。
| 视图名称 | QML 文件 | MainWindow 中的索引 |
|---|---|---|
| 飞行视图 | FlyView.qml | 0 |
| 规划视图 | PlanView.qml | 1 |
| 设置视图 | SettingsView.qml | 2 |
| 分析视图 | AnalyzeView.qml | 3 |
我个人建议,如果你要做二次开发,比如加一个自定义页面,不要直接改 MainWindow 的 C++ 代码。更好的做法是在 QML 层通过 mainWindow.pushPage() 来动态添加。这样既保持了 C++ 层的稳定,又方便后续升级。
3.3 QmlEngine:C++ 和 QML 的桥梁
QmlEngine 是 QGC 中 QQmlApplicationEngine 的封装。它的核心任务就一个:把 C++ 对象暴露给 QML 使用。
说白了,QML 里那些 QGroundControl、controller、globals 对象,都是通过 QmlEngine 注册进去的。没有它,QML 就只是个画界面的壳,啥也干不了。
技巧:如果你想在 QML 里访问自己的 C++ 类,可以在 QGCApplication::_initForNormalAppStart() 里找到 qmlRegisterType<YourClass>() 的调用位置,照猫画虎加一行就行。
来看一个典型的注册代码:
// 在 QGCApplication 初始化中
qmlRegisterUncreatableType<Vehicle>("QGroundControl", 1, 0, "Vehicle", "Reference only");
qmlRegisterType<QGCQGeoCoordinate>("QGroundControl", 1, 0, "QGCQGeoCoordinate");
嗯,这里要注意,Vehicle 用了 UncreatableType,意思是 QML 里不能直接 new 一个 Vehicle 对象,只能通过 C++ 传引用。这种设计是为了防止 QML 层误操作创建出无效对象。
3.4 SettingsManager:配置的“保险箱”
SettingsManager 管理 QGC 的所有设置项。它把设置分成了几个大类:
- AppSettings:应用级设置(语言、单位、UI 主题)
- FlyViewSettings:飞行视图相关(地图类型、仪表盘布局)
- PlanViewSettings:规划视图相关(默认航点高度、安全距离)
- AutoConnectSettings:自动连接设置(串口、UDP、TCP)
每个设置项都是一个 QmlObjectListModel 或者 Fact 对象。我刚开始接触时觉得这层封装有点重,后来才发现它的好处——所有设置都支持 QML 双向绑定,改个值界面自动刷新。
二次开发常用操作:
- 读取设置:
QGCApplication::instance()->settingsManager()->appSettings()->language()->rawValue() - 写入设置:
settingsManager->flyViewSettings()->defaultMissionAltitude()->setRawValue(100.0) - 监听变化:连接
Fact::valueChanged信号
我曾经在做一个多语言切换功能时,直接修改了 AppSettings 里的 language 值,结果发现界面没有刷新。后来才知道,改完设置后需要调用 QGCApplication::instance()->loadLanguage() 才能生效。这个细节文档里没写,我是翻源码才找到的。
3.5 LinkManager:通信链路的“调度中心”
LinkManager 负责管理所有与无人机之间的通信链路。它支持多种链路类型:
| 链路类型 | 类名 | 典型场景 |
|---|---|---|
| 串口 | SerialLink | 数传电台、USB 直连 |
| UDP | UDPLink | WiFi 连接、模拟器 |
| TCP | TCPLink | 远程连接、桥接 |
| 蓝牙 | BluetoothLink | 移动端连接 |
LinkManager 的核心逻辑其实不复杂:维护一个 QList<SharedLinkInterfacePtr>,提供 createConnectedLink() 和 disconnectAll() 接口。但它的难点在于——链路状态变化时,要通知所有订阅者。
举个例子,当串口断开时,LinkManager 会发出 linkDisconnected() 信号,然后 VehicleManager 收到信号后会把对应的 Vehicle 对象标记为离线,接着 QML 界面上的连接状态图标就会变红。这一连串的事件传递,全靠 LinkManager 的信号机制驱动。
避坑指南:我曾经在自定义链路插件里直接调用了 LinkManager::addLink(),但没有设置正确的 LinkConfiguration,结果链路虽然添加成功了,但一直连不上。后来发现 LinkConfiguration 里的 setDynamic() 方法必须调用,否则链路管理器不会自动重连。
3.6 五个类的关系图谱
这五个类不是孤立的,它们之间有明确的依赖关系。我画了一张图,帮你理清它们之间的协作逻辑:
从这张图可以看得很清楚:QGCApplication 是顶层入口,它创建并管理其他四个类。MainWindow 和 QmlEngine 配合完成界面渲染,SettingsManager 提供配置数据,LinkManager 提供通信链路状态。它们之间通过信号-槽机制解耦,这也是 QGC 架构设计的一个亮点。
3.7 二次开发中的常见操作模式
基于这五个核心类,二次开发通常有几种固定的操作模式。我总结了一下:
| 开发场景 | 涉及的核心类 | 典型操作 |
|---|---|---|
| 添加自定义设置项 | SettingsManager | 继承 SettingsGroup,注册 Fact |
| 新增通信协议 | LinkManager | 实现 LinkInterface,注册到工厂 |
| 自定义主界面 | MainWindow + QmlEngine | 添加 QML 页面,注册 C++ 上下文 |
| 全局状态监控 | QGCApplication | 监听 applicationStateChanged 信号 |
嗯,这里要特别提醒一下。很多新手在做二次开发时,喜欢直接修改核心类的源码。我个人建议,除非万不得已,否则尽量通过继承和信号-槽机制来扩展功能。这样当 QGC 官方版本升级时,你的代码冲突会少很多。
举个例子,如果你想在链路断开时弹个提示框,不要改 LinkManager 的源码。正确的做法是:在你的插件里连接 LinkManager::linkDisconnected 信号,然后在槽函数里弹出提示。这样既实现了功能,又保持了核心类的纯净。
技巧:在 QGC 源码里搜索 qgcApp()-> 和 linkManager()->,你会发现大量现成的调用模式。这些就是最好的学习素材,比看文档管用多了。
好了,这五个核心类就讲到这里。它们就像 QGC 的“五脏六腑”,搞清楚了它们各自负责什么、怎么协作,后面的二次开发就会顺手很多。