【QGC】深入解析 QGC 配置管理
引言
在软件开发中,配置管理是一项至关重要的任务,它能帮助我们灵活地管理应用程序的各种参数和设置。QGroundControl(QGC)作为一款强大的开源无人机地面站软件,其配置管理系统设计精巧,值得我们深入学习。本文将详细介绍 QGC 配置管理的核心技术,并结合代码示例进行讲解。
一、QGC 配置管理概述
QGC 的配置管理主要通过 SettingsGroup
类及其相关宏来实现。这些机制允许开发者将不同的设置分组管理,同时支持设置项的创建、访问和可见性控制。下面我们逐步分析其关键组成部分。
1. 头文件保护与依赖引入
SettingsGroup.h
#pragma once
#include "SettingsFact.h"
#pragma once
确保头文件只被编译一次,避免重复包含。SettingsFact.h
引入了处理设置项的相关类。
2. 宏定义设置组名称和 QSettings 组名
SettingsGroup.h
#define DEFINE_SETTING_NAME_GROUP() \static const char* name; \static const char* settingsGroup;
该宏用于在设置组类中声明静态成员变量 name
和 settingsGroup
,分别表示设置组的名称和在 QSettings
中使用的组名。
3. 声明设置组类
SettingsGroup.h
#define DECLARE_SETTINGGROUP(NAME, GROUP) \const char* NAME ## Settings::name = #NAME; \const char* NAME ## Settings::settingsGroup = GROUP; \NAME ## Settings::NAME ## Settings(QObject* parent) \: SettingsGroup(name, settingsGroup, parent)
此宏声明了设置组类的静态成员变量 name
和 settingsGroup
的定义,并定义了设置组类的构造函数。
4. 声明设置项事实
SettingsGroup.h
#define DECLARE_SETTINGSFACT(CLASS, NAME) \const char* CLASS::NAME ## Name = #NAME; \Fact* CLASS::NAME() \{ \if (!_ ## NAME ## Fact) { \_ ## NAME ## Fact = _createSettingsFact(NAME ## Name); \} \return _ ## NAME ## Fact; \}
该宏声明了设置项事实的名称和访问函数,确保设置项事实对象只被创建一次。
5. 定义设置项事实
SettingsGroup.h
#define DEFINE_SETTINGFACT(NAME) \private: \SettingsFact* _ ## NAME ## Fact = nullptr; \public: \Q_PROPERTY(Fact* NAME READ NAME CONSTANT) \Fact* NAME(); \static const char* NAME ## Name;
此宏在设置组类中定义设置项事实的私有成员变量、Qt 属性和访问函数。
6. SettingsGroup 类实现
SettingsGroup.h
class SettingsGroup : public QObject
{Q_OBJECTpublic:SettingsGroup(const QString &name, const QString &settingsGroup, QObject* parent = nullptr);Q_PROPERTY(bool visible READ visible WRITE setVisible NOTIFY visibleChanged)virtual bool visible () { return _visible; }virtual void setVisible (bool vis) { _visible = vis; emit visibleChanged(); }signals:void visibleChanged ();protected:SettingsFact* _createSettingsFact(const QString& factName);bool _visible;QString _name;QString _settingsGroup;QMap<QString, FactMetaData*> _nameToMetaDataMap;private:static constexpr const char* kJsonFile = ":/json/%1.SettingsGroup.json";
};
SettingsGroup
类是配置管理的核心类,提供了设置组的创建、可见性控制等功能。
二、配置管理技术方案
1. 类继承与模块化设计
理论
类继承是面向对象编程的核心特性之一,子类可以继承父类的属性和方法,实现代码复用和功能扩展。模块化设计将系统划分为独立的模块,每个模块负责特定功能,提高代码的可维护性和可扩展性。
应用场景
在配置管理中,当多个配置类有共同的属性和方法时,可定义一个基类来封装这些通用功能,子类继承基类并添加各自特有的配置项和方法。
示例代码
// app_settings_example.h// 基类:SettingsGroup
class SettingsGroup {
public:SettingsGroup() = default;virtual ~SettingsGroup() = default;virtual void loadSettings() = 0;virtual void saveSettings() = 0;
};// 子类:AppSettings
class AppSettings : public SettingsGroup {
public:AppSettings() = default;~AppSettings() override = default;void loadSettings() override {// 实现加载应用设置的逻辑std::cout << "Loading app settings..." << std::endl;}void saveSettings() override {// 实现保存应用设置的逻辑std::cout << "Saving app settings..." << std::endl;}
};
2. 宏定义配置项
理论
宏定义是 C++ 预处理阶段的文本替换机制,通过定义宏可以简化代码编写,提高代码的可维护性和可读性。
应用场景
在配置管理中,当需要批量定义相似的配置项时,使用宏可以减少重复代码。
示例代码
// app_settings_example.h#include <iostream>
#include <string>// 定义宏
#define DEFINE_SETTINGFACT(name) \
private: \std::string name##Value; \
public: \void set##name(const std::string& value) { name##Value = value; } \std::string get##name() const { return name##Value; }class AppSettings {DEFINE_SETTINGFACT(offlineEditingFirmwareClass)DEFINE_SETTINGFACT(offlineEditingVehicleClass)
};
3. Qt 属性系统
理论
Qt 属性系统是 Qt 框架提供的一种机制,允许在类中定义属性,这些属性可以像普通成员变量一样使用,同时支持信号-槽机制和 QML 绑定。
应用场景
在 Qt 应用程序的配置管理中,使用属性系统可以方便地将配置项暴露给 QML 界面,实现界面与后端数据的同步。
示例代码
// app_settings_example.h#include <QObject>
#include <QString>class AppSettings : public QObject {Q_OBJECTQ_PROPERTY(QString missionSavePath READ missionSavePath WRITE setMissionSavePath NOTIFY missionSavePathChanged)public:explicit AppSettings(QObject* parent = nullptr) : QObject(parent) {}QString missionSavePath() const { return m_missionSavePath; }void setMissionSavePath(const QString& path) {if (m_missionSavePath != path) {m_missionSavePath = path;emit missionSavePathChanged();}}signals:void missionSavePathChanged();private:QString m_missionSavePath;
};
4. 信号与槽机制
理论
信号与槽是 Qt 框架的核心机制之一,用于对象间的通信。当信号被发射时,与之连接的槽函数会被自动调用。
应用场景
在配置管理中,当配置项发生变化时,通过发射信号通知其他对象更新状态。
示例代码
// app_settings_example.h#include <QObject>
#include <QString>class AppSettings : public QObject {Q_OBJECT
public:explicit AppSettings(QObject* parent = nullptr) : QObject(parent) {}void changeSetting() {// 模拟配置变化emit settingChanged();}signals:void settingChanged();
};class SettingListener : public QObject {Q_OBJECT
public:explicit SettingListener(QObject* parent = nullptr) : QObject(parent) {}public slots:void onSettingChanged() {std::cout << "Setting has been changed." << std::endl;}
};
5. 常量定义
理论
常量定义用于定义程序中不会改变的值,使用 const
或 constexpr
关键字保证值的不可修改性,提高代码的安全性和可读性。
应用场景
在配置管理中,文件扩展名、子目录名称等固定值通常使用常量定义。
示例代码
// app_settings_example.h#include <iostream>class AppSettings {
public:static constexpr const char* parameterFileExtension = "params";static constexpr const char* parameterDirectory = "Parameters";
};
6. 工具函数
理论
工具函数是为了实现特定功能而封装的独立函数,提高代码的复用性和可维护性。
应用场景
在配置管理中,类型转换、状态标记等操作可以封装成工具函数。
示例代码
// app_settings_example.h#include <QVariant>
#include <QList>class AppSettings {
public:static QList<int> firstRunPromptsIdsVariantToList(const QVariant& firstRunPromptIds) {QList<int> ids;if (firstRunPromptIds.canConvert<QList<int>>()) {ids = firstRunPromptIds.value<QList<int>>();}return ids;}static QVariant firstRunPromptsIdsListToVariant(const QList<int>& rgIds) {return QVariant::fromValue(rgIds);}
};
7. 友元类设计
理论
友元类是一种特殊的类,它可以访问另一个类的私有成员,打破了类的封装性,增强了类之间的协作能力。
应用场景
在配置管理中,当某个类需要频繁访问另一个类的私有成员时,可以将其声明为友元类。
示例代码
// app_settings_example.hclass AppSettings {
private:int privateSetting = 0;friend class QGCApplication;
};class QGCApplication {
public:void accessPrivateSetting(AppSettings& settings) {// 可以访问 AppSettings 的私有成员settings.privateSetting = 10;}
};
完整示例整合
// main.cpp#include <iostream>
#include <QObject>
#include <QString>
#include <QVariant>
#include <QList>// 基类:SettingsGroup
class SettingsGroup {
public:SettingsGroup() = default;virtual ~SettingsGroup() = default;virtual void loadSettings() = 0;virtual void saveSettings() = 0;
};// 定义宏
#define DEFINE_SETTINGFACT(name) \
private: \std::string name##Value; \
public: \void set##name(const std::string& value) { name##Value = value; } \std::string get##name() const { return name##Value; }// 子类:AppSettings
class AppSettings : public SettingsGroup, public QObject {Q_OBJECTQ_PROPERTY(QString missionSavePath READ missionSavePath WRITE setMissionSavePath NOTIFY missionSavePathChanged)public:explicit AppSettings(QObject* parent = nullptr) : QObject(parent) {}void loadSettings() override {std::cout << "Loading app settings..." << std::endl;}void saveSettings() override {std::cout << "Saving app settings..." << std::endl;}DEFINE_SETTINGFACT(offlineEditingFirmwareClass)DEFINE_SETTINGFACT(offlineEditingVehicleClass)QString missionSavePath() const { return m_missionSavePath; }void setMissionSavePath(const QString& path) {if (m_missionSavePath != path) {m_missionSavePath = path;emit missionSavePathChanged();}}void changeSetting() {emit settingChanged();}static QList<int> firstRunPromptsIdsVariantToList(const QVariant& firstRunPromptIds) {QList<int> ids;if (firstRunPromptIds.canConvert<QList<int>>()) {ids = firstRunPromptIds.value<QList<int>>();}return ids;}static QVariant firstRunPromptsIdsListToVariant(const QList<int>& rgIds) {return QVariant::fromValue(rgIds);}static constexpr const char* parameterFileExtension = "params";static constexpr const char* parameterDirectory = "Parameters";private:int privateSetting = 0;QString m_missionSavePath;signals:void missionSavePathChanged();void settingChanged();friend class QGCApplication;
};class SettingListener : public QObject {Q_OBJECT
public:explicit SettingListener(QObject* parent = nullptr) : QObject(parent) {}public slots:void onSettingChanged() {std::cout << "Setting has been changed." << std::endl;}
};class QGCApplication {
public:void accessPrivateSetting(AppSettings& settings) {settings.privateSetting = 10;}
};#include "main.moc"int main() {AppSettings appSettings;SettingListener listener;QObject::connect(&appSettings, &AppSettings::settingChanged, &listener, &SettingListener::onSettingChanged);appSettings.loadSettings();appSettings.setofflineEditingFirmwareClass("FirmwareClass");std::cout << "Offline Editing Firmware Class: " << appSettings.getofflineEditingFirmwareClass() << std::endl;appSettings.setMissionSavePath("NewMissionSavePath");appSettings.changeSetting();appSettings.saveSettings();QList<int> ids = {1, 2, 3};QVariant variantIds = AppSettings::firstRunPromptsIdsListToVariant(ids);QList<int> convertedIds = AppSettings::firstRunPromptsIdsVariantToList(variantIds);for (int id : convertedIds) {std::cout << "ID: " << id << std::endl;}QGCApplication qgcApp;qgcApp.accessPrivateSetting(appSettings);return 0;
}
总结
通过上述分析和示例,我们了解了 QGC 配置管理的核心机制,包括宏定义的使用、SettingsGroup
类的实现以及如何创建和使用设置组与设置项。这种设计使得 QGC 的配置管理具有良好的可扩展性和可维护性,开发者可以方便地添加新的设置组和设置项。希望本文能帮助你更好地理解 QGC 配置管理,并在自己的项目中应用相关技术。