上位机每次启动时,通常需要恢复上一次使用的串口号、波特率、设备地址和保存目录。
如果这些参数全部写在代码中,每换一台设备就要重新编译;如果只保存在界面控件里,程序关闭后又会丢失。
Qt 提供的 QSettings 可以完成这类配置读写。对于单机上位机,使用 INI 文件保存配置,比较方便查看和排查问题。
一、选择配置文件的保存位置
下面以 Qt 6 为例,使用明确的 INI 文件路径,避免不同平台默认存储方式带来的差异。
先在程序入口设置应用信息:
#include <QApplication>
#include <QCoreApplication>
int main(int argc, char* argv[])
{
QApplication app(argc, argv);
QCoreApplication::setOrganizationName("DeviceTools");
QCoreApplication::setApplicationName("DeviceMonitor");
// 创建并显示主窗口……
return app.exec();
}
获取配置文件路径:
#include <QStandardPaths>
#include <QDir>
#include <QString>
QString configFilePath()
{
const QString directory =
QStandardPaths::writableLocation(
QStandardPaths::AppConfigLocation);
if (directory.isEmpty() || !QDir().mkpath(directory))
return {};
return QDir(directory).filePath("settings.ini");
}
使用系统提供的配置目录,可以减少程序安装目录没有写权限的问题。
不建议直接使用:
QSettings settings("settings.ini", QSettings::IniFormat);
这样的相对路径取决于程序当前工作目录。从 IDE、快捷方式和命令行启动时,最终读取的可能不是同一个文件。
二、保存通信参数
先定义设备配置:
struct DeviceConfig {
QString name = "新设备";
QString portName;
int baudRate = 115200;
bool autoConnect = false;
};
判断波特率是否在当前项目支持的范围内:
bool isSupportedBaudRate(int value)
{
switch (value) {
case 9600:
case 19200:
case 38400:
case 57600:
case 115200:
return true;
default:
return false;
}
}
这个范围属于项目约定。设备使用其他波特率时,需要相应补充。
保存函数:
#include <QSettings>
#include <QRegularExpression>
bool saveDeviceConfig(const QString& filePath,
const QString& deviceId,
const DeviceConfig& config)
{
static const QRegularExpression idPattern(
"^[A-Za-z0-9_-]+$");
if (filePath.isEmpty() ||
!idPattern.match(deviceId).hasMatch() ||
!isSupportedBaudRate(config.baudRate)) {
return false;
}
QSettings settings(filePath, QSettings::IniFormat);
settings.beginGroup("devices");
settings.beginGroup(deviceId);
settings.setValue("name", config.name);
settings.setValue("portName", config.portName.trimmed());
settings.setValue("baudRate", config.baudRate);
settings.setValue("autoConnect", config.autoConnect);
settings.endGroup();
settings.endGroup();
settings.sync();
return settings.status() == QSettings::NoError;
}
调用示例:
DeviceConfig config;
config.name = "温度采集模块";
config.portName = "COM3";
config.baudRate = 115200;
config.autoConnect = false;
const QString path = configFilePath();
if (!saveDeviceConfig(path, "sensor_01", config))
qWarning() << "配置保存失败";
setValue() 不代表数据已经成功写入磁盘。用户点击“保存”后,可以调用 sync(),再检查 status(),避免界面提示保存成功,实际却因权限或磁盘问题没有写入。
三、读取配置与处理默认值
首次运行时没有配置文件,直接使用默认值即可。但还要考虑配置文件被手动修改,出现非法内容的情况。
例如,下面这行配置不是合法波特率:
baudRate=abc
读取时需要同时检查转换结果和业务范围:
DeviceConfig loadDeviceConfig(QSettings& settings,
const QString& deviceId)
{
DeviceConfig config;
settings.beginGroup("devices");
settings.beginGroup(deviceId);
config.name = settings.value(
"name", config.name).toString();
config.portName = settings.value(
"portName", QString()).toString().trimmed();
bool converted = false;
const int baudRate = settings.value(
"baudRate", config.baudRate).toInt(&converted);
if (converted && isSupportedBaudRate(baudRate))
config.baudRate = baudRate;
const QString autoConnect = settings.value(
"autoConnect", false).toString().trimmed().toLower();
config.autoConnect =
autoConnect == "true" || autoConnect == "1";
settings.endGroup();
settings.endGroup();
return config;
}
使用时:
const QString path = configFilePath();
if (path.isEmpty()) {
qWarning() << "无法创建配置目录";
return;
}
QSettings settings(path, QSettings::IniFormat);
const DeviceConfig config =
loadDeviceConfig(settings, "sensor_01");
if (settings.status() != QSettings::NoError)
qWarning() << "配置文件读取异常";
这里有两种不同情况:
- 配置项不存在:使用默认值。
- 配置项存在但不合法:校验失败后回退到默认值。
如果只是写 value("baudRate", 115200).toInt(),遇到 "abc" 时可能得到 0,并不会自动回退成 115200。
四、管理多台设备并恢复界面
同一个 INI 文件中,可以按照设备编号分别保存配置:
devices/sensor_01
devices/sensor_02
devices/controller_01
设备编号应保持稳定,显示名称可以修改。不要把“温度传感器”这样的显示名称直接作为唯一标识,否则用户改名后就可能找不到原来的配置。
列出所有设备配置:
settings.beginGroup("devices");
const QStringList deviceIds = settings.childGroups();
settings.endGroup();
for (const QString& deviceId : deviceIds) {
const DeviceConfig config =
loadDeviceConfig(settings, deviceId);
qDebug() << deviceId << config.name << config.portName;
}
恢复界面时,还要避免控件变化信号触发不必要的保存或连接操作:
#include <QSignalBlocker>
{
const QSignalBlocker blockPort(ui->portComboBox);
const QSignalBlocker blockBaud(ui->baudRateComboBox);
const int portIndex =
ui->portComboBox->findData(config.portName);
ui->portComboBox->setCurrentIndex(portIndex);
const int baudIndex =
ui->baudRateComboBox->findData(config.baudRate);
ui->baudRateComboBox->setCurrentIndex(baudIndex);
}
这里假设下拉框的用户数据分别保存端口名称和整数波特率。
如果原来的串口已经不存在,应提示用户重新选择,不要悄悄改成列表中的第一个串口。自动连接也应等配置恢复、端口检查完成后,再由明确的启动逻辑执行。
五、配置管理中的几个注意点
不要把高频采样数据写进配置文件。
QSettings 适合通信参数、窗口状态和用户偏好。连续采集记录应使用数据库或数据文件。
不要在每次控件变化时都强制落盘。
可以在点击“应用”时统一保存,或者延迟合并短时间内的多次修改。窗口尺寸等变化频繁的配置,更适合在操作结束或退出时保存。
配置文件不是加密存储。
INI 文件通常可以直接查看。设备密码、访问令牌等内容,不应因为放进 QSettings 就被认为已经安全保存。
配置结构变化时保留迁移空间。
项目迭代后,字段名称和含义可能变化。可以保存一个 configVersion,新版本启动时按版本迁移,避免把旧配置直接解释成新含义。
配置管理的重点是让程序在首次运行、参数缺失和配置异常时,都有明确且可预测的行为。