从零构建INI配置工具的分步指南

一、需求分析与技术选型

核心需求 :创建一个可视化工具,支持INI配置文件的创建、编辑、保存和加载,需处理多种数据类型(布尔值、整数、浮点数、枚举、字符串)。

技术选型 :

  • 框架 :Qt 5/6(推荐5.15+,提供完善的UI组件和文件操作API)
  • 语言 :C++17(现代C++特性提升开发效率)
  • 构建工具 :Qt Creator(集成开发环境,简化UI设计)
  • 编码标准 :UTF-8(解决中文显示问题)

二、项目初始化,创建Qt Widgets项目

js 复制代码
使用Qt Creator创建新项目:
1. 选择"Qt Widgets Application"
2. 项目名称:config_editor
3. 基类选择QWidget,类名ConfigEditor
4. 勾选"Generate form"

三、配置项目文件(.pro)

js 复制代码
QT       += core gui widgets
CONFIG += c++17
QMAKE_CXXFLAGS += /utf-8  # 解决中文编译乱码

SOURCES += main.cpp configeditor.cpp
HEADERS += configeditor.h
FORMS += configeditor.ui  

四、实现入口函数(main.cpp)

js 复制代码
#include "configeditor.h"
#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    ConfigEditor w;
    w.show();
    return a.exec();
}

五、核心数据模型设计(configeditor.h)

5.1. 头文件基础结构

js 复制代码
#ifndef CONFIGEDITOR_H  // 头文件保护宏,防止重复包含
#define CONFIGEDITOR_H

#include <QWidget>      // 基础窗口部件类
#include <QMap>         // 键值对容器,用于存储枚举选项和控件映射
#include <QVariant>     // 通用数据类型,支持多类型参数值存储
#include <QList>        // 动态数组容器,用于存储参数列表
#include <QString>      // 字符串类,用于处理文本数据
  • 头文件保护宏 : CONFIGEDITOR_H 确保文件只被编译一次,避免重复定义错误
  • Qt库依赖 :包含Qt核心GUI组件和数据结构,为跨平台界面开发提供支持

5.2. 核心数据模型: Parameter 结构体

js 复制代码
// 参数结构体定义(与项目代码完全一致)
struct Parameter {
    QString name;       // 参数名(INI键)
    QString type;       // 参数类型(bool/int/double/enum/string)
    QVariant value;     // 当前值
    QString comment;    // 中文描述(用户选中的当前行)
    QMap<QString, QVariant> options; // 枚举选项(文本→值映射)
};
  • name :INI文件中的键名(如 "AutoSave" ),用于文件IO和控件标识
  • type :参数类型字符串(而非枚举),决定UI控件类型和数据验证规则
  • value :使用 QVariant 存储多类型值(bool/int/字符串等),实现类型安全存储
  • comment :中文描述(用户当前选中行),作为UI标签提示用户参数含义
  • options :仅枚举类型使用,存储下拉选项(如 {"启用": true, "禁用": false} ) 设计亮点 :通过字符串类型标识和 QVariant 实现灵活的多类型参数系统,无需修改代码即可扩展新参数类型。

5.3. 主窗口类: ConfigEditor

继承自 QWidget ,是应用程序的主界面和控制器,协调数据模型与UI交互。

(1) 类声明与信号槽

js 复制代码
class ConfigEditor : public QWidget
{
    Q_OBJECT  // Qt元对象系统宏,支持信号槽机制

public:
    explicit ConfigEditor(QWidget *parent = nullptr);
    ~ConfigEditor() override;

private slots:
    void on_btnSave_clicked();    // 保存按钮点击事件
    void on_btnOpen_clicked();    // 打开按钮点击事件
  • 构造函数 :初始化窗口、参数系统和UI控件
  • 析构函数 :释放动态分配的资源(如UI指针)
  • 私有槽函数 :响应按钮点击事件,实现文件保存/加载功能

(2) 核心业务方法

js 复制代码
private:
    void initParameters();        // 初始化参数定义
    void createControls();        // 动态创建UI控件
    bool saveIniFile(const QString& filePath); // 保存INI文件
    bool loadIniFile(const QString& filePath); // 加载INI文件
    void updateParametersFromControls(); // 从控件同步数据到参数
    void updateControlsFromParameters(); // 从参数同步数据到控件
  • initParameters() :定义所有配置项元数据(如日志级别、波特率等),是数据驱动设计的核心
  • createControls() :根据 parameters 列表动态生成对应UI控件(复选框/文本框/下拉框等)
  • 文件IO方法 :处理UTF-8编码的INI文件读写,确保中文正常显示
  • 数据同步方法 :维护UI控件与参数列表间的双向数据一致性

(2) 成员变量

js 复制代码
    QList<Parameter> parameters;  // 参数列表(核心数据存储)
    QMap<QString, QWidget*> controlMap; // 参数名→控件映射表
    Ui::ConfigEditor *ui;         // UI表单指针(由uic生成)
  • parameters :存储所有配置参数的列表,是应用程序的状态核心
  • controlMap :建立参数名到控件的快速查找,实现高效数据同步
  • ui :指向由 configeditor.ui 生成的界面元素(如按钮、布局容器)

5.4. 完整代码

js 复制代码
#ifndef CONFIGEDITOR_H
#define CONFIGEDITOR_H

#include <QWidget>
#include <QMap>
#include <QVariant>
#include <QList>
#include <QString>

// 参数结构体定义(与项目代码完全一致)
struct Parameter {
    QString name;       // 参数名(INI键)
    QString type;       // 参数类型(bool/int/double/enum/string)
    QVariant value;     // 当前值
    QString comment;    // 中文描述(用户选中的当前行)
    QMap<QString, QVariant> options; // 枚举选项(文本→值映射)
};

class ConfigEditor : public QWidget
{
    Q_OBJECT

public:
    explicit ConfigEditor(QWidget *parent = nullptr);
    ~ConfigEditor() override;

private slots:
    void on_btnSave_clicked();    // 保存按钮点击事件
    void on_btnOpen_clicked();    // 打开按钮点击事件

private:
    void initParameters();        // 初始化参数定义
    void createControls();        // 动态创建UI控件
    bool saveIniFile(const QString& filePath); // 保存INI文件
    bool loadIniFile(const QString& filePath); // 加载INI文件
    void updateParametersFromControls(); // 从控件同步数据到参数
    void updateControlsFromParameters(); // 从参数同步数据到控件

    QList<Parameter> parameters;  // 参数列表(核心数据存储)
    QMap<QString, QWidget*> controlMap; // 参数名→控件映射表
    Ui::ConfigEditor *ui;         // UI表单指针(由uic生成)
};

#endif // CONFIGEDITOR_H

六、实现ConfigEditor核心功能(configeditor.cpp)

6.1. 构造函数 ConfigEditor::ConfigEditor

js 复制代码
ConfigEditor::ConfigEditor(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::ConfigEditor)
{
    ui->setupUi(this);                  // 初始化UI表单
    setWindowTitle("INI配置工具");         // 设置窗口标题
    resize(900, 700);                   // 窗口初始大小

    // 全局字体设置(解决中文显示问题)
    QFont globalFont("SimSun", 12);
    this->setFont(globalFont);

    initParameters();                   // 初始化参数定义
    createControls();                   // 动态创建UI控件

    // 信号槽防重复连接处理
    disconnect(ui->btnSave, SIGNAL(clicked()), this, SLOT(on_btnSave_clicked()));
    connect(ui->btnSave, &QPushButton::clicked, this, &ConfigEditor::on_btnSave_clicked);
    disconnect(ui->btnOpen, SIGNAL(clicked()), this, SLOT(on_btnOpen_clicked()));
    connect(ui->btnOpen, &QPushButton::clicked, this, &ConfigEditor::on_btnOpen_clicked);
}

核心功能 :

  • 完成UI初始化与窗口设置
  • 配置全局字体(宋体12号)确保中文正常显示
  • 调用 initParameters() 加载参数元数据
  • 调用 createControls() 动态生成控件
  • 信号槽连接前先断开已有连接,解决Qt可能的重复连接问题

6.2. 析构函数 ConfigEditor::~ConfigEditor

js 复制代码
ConfigEditor::~ConfigEditor()
{
    delete ui;  // 释放UI指针资源
}

核心功能 :释放由 ui 指针指向的动态分配UI资源,防止内存泄漏。

6.3. 参数初始化 ConfigEditor::initParameters

js 复制代码
void ConfigEditor::initParameters()
{
    parameters = {  // 初始化参数列表(完整版本)
        {"isDebug", "bool", 1, "调试模式开关", {}},
        {"nRotate", "enum", -1, "旋转角度", {{"不旋转", -1}, {"90度", 0}}},
        // ... 其他50+参数定义 ...
        {"revReworkIP", "string", "192.168.60.80", "复判接收IP", {}}
    };
}

定义义所有配置参数的元数据,包括:

  • name :INI文件中的键名
  • type :参数类型(bool/int/double/enum/string)
  • value :默认值
  • comment :中文描述
  • options :枚举类型的键值对选项
  • 项目实际包含50+参数,覆盖系统设置、路径配置、网络参数等

6.4. 动态控件创建 ConfigEditor::createControls

js 复制代码
void ConfigEditor::createControls()
{
    QFormLayout *layout = new QFormLayout(ui->scrollAreaWidgetContents);
    layout->setAlignment(Qt::AlignCenter);  // 布局居中对齐
    layout->setSpacing(12);                 // 控件间距
    layout->setContentsMargins(20, 20, 20, 20);

    QFont labelFont("SimSun", 12);         // 标签字体
    QFont controlFont("SimSun", 12);       // 控件字体

    for (const auto& param : parameters) {
        QLabel *label = new QLabel(param.name + "\n(" + param.comment + ")");
        label->setFont(labelFont);
        label->setWordWrap(true);          // 标签自动换行

        QWidget *control = nullptr;
        // 根据参数类型创建对应控件
        if (param.type == "bool") {
            QCheckBox *checkBox = new QCheckBox;
            checkBox->setChecked(param.value.toInt() == 1);
            control = checkBox;
        } else if (param.type == "int") {
            QSpinBox *spinBox = new QSpinBox;
            spinBox->setRange(-1000000, 100000000);
            spinBox->setValue(param.value.toInt());
            control = spinBox;
        // ... double/enum/string类型控件创建 ...
        }

        if (control) {
            layout->addRow(label, control);
            controlMap[param.name] = control;  // 建立参数名→控件映射
        }
    }
}
  • 创建表单布局并设置样式(居中对齐、间距、边距)
  • 根据参数类型动态生成对应UI控件:
  • bool → QCheckBox (勾选状态对应1/0值)
  • int → QSpinBox (设置大范围数值限制)
  • double → QDoubleSpinBox (支持小数输入)
  • enum → QComboBox (加载预设选项)
  • string → QLineEdit (文本输入)
  • 通过 controlMap 建立参数名与控件的映射关系,支持后续数据同步

6.5. INI文件保存 ConfigEditor::saveIniFile

js 复制代码
bool ConfigEditor::saveIniFile(const QString& filePath)
{
    updateParametersFromControls();  // 先从控件同步数据

    QFile file(filePath);
    if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) return false;

    QTextStream out(&file);
    out.setCodec("UTF-8");          // 设置UTF-8编码防中文乱码
    out << "[System]\n";            // 写入节标题

    // 循环写入参数,最后一个参数不添加换行符
    for (int i = 0; i < parameters.size(); ++i) {
        const auto& param = parameters[i];
        out << param.name << "=" << param.value.toString();
        if (!param.comment.isEmpty()) {
            out << " ; " << param.comment;  // 添加注释
        }
        if (i != parameters.size() - 1) out << "\n";  // 避免末尾空行
    }

    file.close();
    return true;
}

核心功能 :

  • 调用 updateParametersFromControls() 确保数据最新
  • 使用 QTextStream 并显式设置UTF-8编码
  • 严格遵循INI格式:
    • 以 [System] 为节标题
    • 键值对格式 name=value
    • 尾部添加 ; 注释
    • 避免文件末尾产生多余空行
  • 返回布尔值指示保存成功与否

6.6. INI文件加载 ConfigEditor::loadIniFile

js 复制代码
bool ConfigEditor::loadIniFile(const QString& filePath)
{
    QFile file(filePath);
    if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) return false;

    QTextStream in(&file);
    in.setCodec("UTF-8");           // UTF-8编码读取
    while (!in.atEnd()) {
        QString line = in.readLine().trimmed();
        if (line.isEmpty() || line.startsWith(';')) continue;  // 跳过空行和注释

        int eqIndex = line.indexOf('=');
        if (eqIndex <= 0) continue;  // 跳过无效行

        QString key = line.left(eqIndex).trimmed();
        QString value = line.mid(eqIndex + 1).trimmed();
        // 移除行内注释
        int commentIndex = value.indexOf(';');
        if (commentIndex != -1) value = value.left(commentIndex).trimmed();

        // 更新参数值(根据类型转换)
        for (auto& param : parameters) {
            if (param.name == key) {
                if (param.type == "bool") param.value = value.toInt();
                else if (param.type == "int") param.value = value.toInt();
                // ... 其他类型转换 ...
                break;
            }
        }
    }

    file.close();
    updateControlsFromParameters();  // 更新控件显示
    return true;
}

核心功能 :

  • 读取并解析INI文件,支持UTF-8编码
  • 跳过空行和注释行,提取有效键值对
  • 根据参数类型进行值转换(字符串→目标类型)
  • 调用 updateControlsFromParameters() 刷新UI显示

6.7. 数据同步:控件→参数 ConfigEditor::updateParametersFromControls

js 复制代码
void ConfigEditor::updateParametersFromControls()
{
    for (auto& param : parameters) {
        if (controlMap.contains(param.name)) {
            QWidget *control = controlMap[param.name];

            if (param.type == "bool") {
                QCheckBox *checkBox = qobject_cast<QCheckBox*>(control);
                if (checkBox) param.value = checkBox->isChecked() ? 1 : 0;
            } else if (param.type == "int") {
                QSpinBox *spinBox = qobject_cast<QSpinBox*>(control);
                if (spinBox) param.value = spinBox->value();
            // ... 其他类型同步 ...
            }
        }
    }
}

核心功能 :遍历控件映射表,根据参数类型从对应控件中读取值并更新到 parameters 列表。

6.8. 数据同步:参数→控件 ConfigEditor::updateControlsFromParameters

js 复制代码
void ConfigEditor::updateControlsFromParameters()
{
    for (const auto& param : parameters) {
        if (controlMap.contains(param.name)) {
            QWidget *control = controlMap[param.name];

            if (param.type == "bool") {
                QCheckBox *checkBox = qobject_cast<QCheckBox*>(control);
                if (checkBox) checkBox->setChecked(param.value.toInt() == 1);
            // ... 其他类型同步 ...
            }
        }
    }
}

核心功能 :将 parameters 列表中的值同步到对应UI控件,实现数据显示更新。

6.9. 打开按钮槽函数 ConfigEditor::on_btnOpen_clicked

js 复制代码
void ConfigEditor::on_btnOpen_clicked()
{
    QString filePath = QFileDialog::getOpenFileName(this, "打开INI文件", ".", "INI Files (*.ini);;All Files (*)");
    if (!filePath.isEmpty()) {
        currentFilePath = filePath;
        if (loadIniFile(filePath)) {
            setWindowTitle("INI配置工具 - " + filePath);
            QMessageBox::information(this, "成功", "文件加载成功");
        } else {
            QMessageBox::warning(this, "失败", "无法加载文件");
        }
    }
}

核心功能 :

  • 弹出文件选择对话框
  • 调用 loadIniFile() 加载选中文件
  • 更新窗口标题并显示操作结果提示

6.10. 保存按钮槽函数 ConfigEditor::on_btnSave_clicked

js 复制代码
void ConfigEditor::on_btnSave_clicked()
{
    QString filePath = currentFilePath;
    if (filePath.isEmpty()) {
        filePath = QFileDialog::getSaveFileName(this, "保存INI文件", ".", "INI Files (*.ini)");
    }

    if (!filePath.isEmpty()) {
        static bool isSaving = false;  // 防重复保存标志
        if (isSaving) return;
        isSaving = true;

        bool success = saveIniFile(filePath);
        if (success) {
            currentFilePath = filePath;
            setWindowTitle("INI配置工具 - " + filePath);
            QMessageBox::information(this, "成功", "文件保存成功");
        } else {
            QMessageBox::warning(this, "失败", "无法保存文件");
        }

        isSaving = false;
    }
}

核心功能 :

  • 若未指定路径则弹出保存对话框
  • 使用静态变量 isSaving 防止并发保存冲突
  • 调用 saveIniFile() 保存数据
  • 显示操作结果提示并更新窗口标题

完整代码

js 复制代码
#include "configeditor.h"
#include "ui_configeditor.h"
#include <QFileDialog>
#include <QMessageBox>
#include <QFormLayout>
#include <QLineEdit>
#include <QCheckBox>
#include <QSpinBox>
#include <QDoubleSpinBox>
#include <QComboBox>
#include <QFile>
#include <QTextStream>
#include <QDebug>

ConfigEditor::ConfigEditor(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::ConfigEditor)
{
    ui->setupUi(this);
    setWindowTitle("INI配置工具");
    resize(900, 700); // 增大窗口默认尺寸

    // 设置全局字体 - 将字体大小从调整为12
    QFont globalFont("SimSun", 12);
    this->setFont(globalFont);

    // 初始化参数定义
    initParameters();

    // 创建控件
    createControls();

    // 确保信号槽只连接一次
    // 先断开可能存在的连接
    disconnect(ui->btnSave, SIGNAL(clicked()), this, SLOT(on_btnSave_clicked()));
    // 再重新连接
    connect(ui->btnSave, &QPushButton::clicked, this, &ConfigEditor::on_btnSave_clicked);

    // 同样修复打开按钮的连接
    disconnect(ui->btnOpen, SIGNAL(clicked()), this, SLOT(on_btnOpen_clicked()));
    connect(ui->btnOpen, &QPushButton::clicked, this, &ConfigEditor::on_btnOpen_clicked);
}

ConfigEditor::~ConfigEditor()
{
    delete ui;
}

void ConfigEditor::initParameters()
{
    // 初始化所有参数定义(完整版本)
    parameters = {
        {"isDebug", "bool", 1, "调试模式开关", {}}, 
        {"isMoreCheckThread", "bool", 1, "启用更多检查线程", {}}, 
        {"isDebugRemote", "bool", 0, "远程调试开关", {}}, 
        {"nProductSplitCount", "int", 1, "产品分割数量", {}}, 
        {"nMaxGerberCount", "int", 1000000, "最大Gerber数量", {}}, 
        {"isMoreBoard", "bool", 0, "多板支持", {}}, 
        {"isUseKeyboard", "bool", 0, "启用键盘控制", {}}, 
        {"isManualScanBarcode", "bool", 0, "手动扫描条形码", {}}, 
        {"isRunDevExe", "bool", 0, "运行开发版程序", {}}, 
        {"isUseMark", "bool", 1, "使用标记点", {}}, 
        {"isUseHMRun", "bool", 1, "启用HM运行模式", {}}, 
        {"isUseSREnhance", "bool", 1, "启用SR增强", {}}, 
        {"isUseSREnhanceRun", "bool", 1, "运行时启用SR增强", {}}, 
        {"isSaveOrgImage", "bool", 1, "保存原始图像", {}}, 
        {"isSaveOrgImageTrain", "bool", 1, "保存训练用原始图像", {}}, 
        {"isShowResultImage", "bool", 0, "显示结果图像", {}}, 
        {"isExchangeGerberXY", "bool", 1, "交换Gerber的X和Y轴", {}}, 
        {"isUseManualCheck", "bool", 1, "启用手动检查", {}}, 
        {"isImportClearOld", "bool", 0, "导入时清空旧元件", {}}, 
        {"isTestRun", "bool", 0, "测试运行模式", {}}, 
        {"isInputPinNo", "bool", 0, "输入引脚编号", {}}, 
        {"runSet", "int", 0, "运行设置", {}}, 
        {"nGerberType", "int", 1, "Gerber类型", {}}, 
        {"timeoutGetSlice", "int", 90, "获取切片超时时间(秒)", {}}, 
        {"dscanOffsetUp", "double", 0.5, "扫描偏移量(上)", {}}, 
        {"dscanOffsetDown", "double", 0.5, "扫描偏移量(下)", {}}, 
        {"dWinExtend", "double", 5, "窗口扩展值", {}}, 
        {"dNavRate", "double", 0.5, "导航速率", {}}, 
        {"bgaAlgoVer", "int", 2, "BGA算法版本", {}}, 
        {"sliceWidth", "int", 1536, "切片宽度", {}}, 
        {"sliceHeight", "int", 1536, "切片高度", {}}, 
        {"nRotate", "enum", -1, "旋转角度: -1不旋转, 0-90度, 1-180度, 2-270度", 
            {{"不旋转", -1}, {"90度", 0}, {"180度", 1}, {"270度", 2}}}, 
        {"nFlip", "enum", 0, "镜像方式: -2不镜像, -1-xy轴, 0-x轴, 1-y轴", 
            {{"不镜像", -2}, {"xy轴镜像", -1}, {"x轴镜像", 0}, {"y轴镜像", 1}}}, 
        {"nGerberCoordinateUnitDefault", "enum", 2, "坐标单位: 0-um, 1-mm, 2-mil, 3-in", 
            {{"微米(um)", 0}, {"毫米(mm)", 1}, {"密耳(mil)", 2}, {"英寸(in)", 3}}}, 
        {"nGerberDiameterUnitDefault", "enum", 0, "直径单位: 0-um, 1-mm, 2-mil, 3-in", 
            {{"微米(um)", 0}, {"毫米(mm)", 1}, {"密耳(mil)", 2}, {"英寸(in)", 3}}}, 
        {"nGerberZUnitDefault", "enum", 1, "Z轴单位: 0-um, 1-mm, 2-mil, 3-in", 
            {{"微米(um)", 0}, {"毫米(mm)", 1}, {"密耳(mil)", 2}, {"英寸(in)", 3}}}, 
        {"dbUnitRateUM", "double", 0.001, "微米单位比率", {}}, 
        {"dbUnitRateMil", "double", 0.00254, "密耳单位比率", {}}, 
        {"dbUnitRateIn", "double", 25.4, "英寸单位比率", {}}, 
        {"nMaxThreadCount", "int", 5, "最大检测线程数量", {}}, 
        {"detectResultImageIndex", "int", 3, "检测结果图像索引", {}}, 
        {"machineName", "string", "AXI9000-0002", "机器名称", {}}, 
        {"deviceExeName", "string", "zmpublicServer.exe", "设备程序名称", {}}, 
        {"deviceExePath", "string", "D:/zmAXI9100/zmpublicServer/zmpublicServer.exe", "设备程序路径", {}}, 
        {"solderModelFile", "string", "D:/zmAXI9100/models/igbt_base_solder.engine", "焊接模型文件路径", {}}, 
        {"voidModelFile", "string", "D:/zmAXI9100/models/igbt_base_void.engine", "空洞模型文件路径", {}}, 
        {"barcodeFilePath", "string", "d:/temp/barcode", "条形码文件路径", {}}, 
        {"SendDevicePort", "int", 5555, "发送设备端口", {}}, 
        {"RevDevicePort", "int", 5556, "接收设备端口", {}}, 
        {"RevDeviceIP", "string", "127.0.0.1", "接收设备IP", {}}, 
        {"redisPort", "int", 6379, "Redis服务器端口", {}}, 
        {"redisIP", "string", "192.168.60.80", "Redis服务器IP", {}}, 
        {"dataServerPort", "int", 9901, "数据服务器端口", {}}, 
        {"dataServerIP", "string", "192.168.60.80", "数据服务器IP", {}}, 
        {"userServerIP", "string", "192.168.60.80", "用户服务器IP", {}}, 
        {"userServerPort", "int", 9903, "用户服务器端口", {}}, 
        {"appMode", "enum", 0, "运行模式: 0-机台软件, 1-检测服务器", 
            {{"机台软件", 0}, {"检测服务器", 1}}}, 
        {"isUseServerRunAlgo", "bool", 0, "使用服务器运行算法", {}}, 
        {"sendPort", "int", 6612, "发送端口", {}}, 
        {"revPort", "int", 6611, "接收端口", {}}, 
        {"revIP", "string", "192.168.60.80", "接收IP", {}}, 
        {"isUseAoiResult", "bool", 1, "是否读取Aoi结果", {}}, 
        {"aoiResultFilePath", "string", "d:/temp/aoi_result", "Aoi结果文件路径", {}}, 
        {"isWaitReworkResult", "bool", 0, "是否等待复判结果", {}}, 
        {"revReworkPort", "int", 9910, "复判接收端口", {}}, 
        {"revReworkIP", "string", "192.168.60.80", "复判接收IP", {}}
    };
}

void ConfigEditor::createControls()
{
    QFormLayout *layout = new QFormLayout(ui->scrollAreaWidgetContents);
    // 设置布局居中对齐
    layout->setAlignment(Qt::AlignCenter);
    // 设置控件间距
    layout->setSpacing(12);
    // 设置布局边距
    layout->setContentsMargins(20, 20, 20, 20);

    // 将字体大小调整为12
    QFont labelFont("SimSun", 12);
    QFont controlFont("SimSun", 12);

    for (const auto& param : parameters) {
        QLabel *label = new QLabel(param.name + "\n(" + param.comment + ")");
        label->setFont(labelFont);
        label->setWordWrap(true);
        label->setMinimumWidth(220); // 增加标签最小宽度
        label->setAlignment(Qt::AlignHCenter | Qt::AlignVCenter);

        QWidget *control = nullptr;

        if (param.type == "bool") {
            QCheckBox *checkBox = new QCheckBox;
            checkBox->setChecked(param.value.toInt() == 1);
            checkBox->setFont(controlFont);
            control = checkBox;
        } else if (param.type == "int") {
            QSpinBox *spinBox = new QSpinBox;
            spinBox->setRange(-1000000, 100000000);
            spinBox->setValue(param.value.toInt());
            spinBox->setFont(controlFont);
            spinBox->setMinimumWidth(150); // 增加控件最小宽度
            control = spinBox;
        } else if (param.type == "double") {
            QDoubleSpinBox *doubleSpinBox = new QDoubleSpinBox;
            doubleSpinBox->setRange(-10000, 10000);
            doubleSpinBox->setDecimals(6);
            doubleSpinBox->setValue(param.value.toDouble());
            doubleSpinBox->setFont(controlFont);
            doubleSpinBox->setMinimumWidth(150); // 增加控件最小宽度
            control = doubleSpinBox;
        } else if (param.type == "enum" && !param.options.isEmpty()) {
            QComboBox *comboBox = new QComboBox;
            comboBox->setFont(controlFont);
            comboBox->setMinimumWidth(180); // 增加枚举控件宽度
            for (auto it = param.options.begin(); it != param.options.end(); ++it) {
                comboBox->addItem(it.key(), it.value());
                if (it.value() == param.value) {
                    comboBox->setCurrentIndex(comboBox->count() - 1);
                }
            }
            control = comboBox;
        } else { // string
            QLineEdit *lineEdit = new QLineEdit(param.value.toString());
            lineEdit->setFont(controlFont);
            lineEdit->setMinimumWidth(250); // 增加文本输入框宽度
            control = lineEdit;
        }

        if (control) {
            // 设置控件居中对齐
            layout->addRow(label, control);
            layout->setAlignment(control, Qt::AlignCenter);
            controlMap[param.name] = control;
        }
    }
}

// 保存文件时设置编码
bool ConfigEditor::saveIniFile(const QString& filePath)
{
    updateParametersFromControls();

    QFile file(filePath);
    if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
        return false;
    }

    QTextStream out(&file);
    out.setCodec("UTF-8"); // 设置UTF-8编码
    // 移除自动生成注释行
    out << "[System]\n"; // 直接写入[System]节标题

    // 循环写入参数,最后一个参数不添加换行符
    for (int i = 0; i < parameters.size(); ++i) {
        const auto& param = parameters[i];
        out << param.name << "=" << param.value.toString();
        if (!param.comment.isEmpty()) {
            out << " ; " << param.comment;
        }
        // 仅在不是最后一个参数时添加换行
        if (i != parameters.size() - 1) {
            out << "\n";
        }
    }

    file.close();
    return true;
}

// 加载文件时设置编码
bool ConfigEditor::loadIniFile(const QString& filePath)
{
    QFile file(filePath);
    if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
        return false;
    }

    QTextStream in(&file);
    in.setCodec("UTF-8"); // 设置UTF-8编码
    while (!in.atEnd()) {
        QString line = in.readLine().trimmed();
        if (line.isEmpty() || line.startsWith(';')) continue;

        int eqIndex = line.indexOf('=');
        if (eqIndex <= 0) continue;

        QString key = line.left(eqIndex).trimmed();
        QString value = line.mid(eqIndex + 1).trimmed();
        // 移除注释部分
        int commentIndex = value.indexOf(';');
        if (commentIndex != -1) {
            value = value.left(commentIndex).trimmed();
        }

        // 更新参数值
        for (auto& param : parameters) {
            if (param.name == key) {
                if (param.type == "bool") {
                    param.value = value.toInt();
                } else if (param.type == "int") {
                    param.value = value.toInt();
                } else if (param.type == "double") {
                    param.value = value.toDouble();
                } else if (param.type == "enum") {
                    param.value = value.toInt();
                } else {
                    param.value = value;
                }
                break;
            }
        }
    }

    file.close();
    updateControlsFromParameters();
    return true;
}

void ConfigEditor::updateParametersFromControls()
{
    for (auto& param : parameters) {
        if (controlMap.contains(param.name)) {
            QWidget *control = controlMap[param.name];

            if (param.type == "bool") {
                QCheckBox *checkBox = qobject_cast<QCheckBox*>(control);
                if (checkBox) param.value = checkBox->isChecked() ? 1 : 0;
            } else if (param.type == "int") {
                QSpinBox *spinBox = qobject_cast<QSpinBox*>(control);
                if (spinBox) param.value = spinBox->value();
            } else if (param.type == "double") {
                QDoubleSpinBox *doubleSpinBox = qobject_cast<QDoubleSpinBox*>(control);
                if (doubleSpinBox) param.value = doubleSpinBox->value();
            } else if (param.type == "enum") {
                QComboBox *comboBox = qobject_cast<QComboBox*>(control);
                if (comboBox) param.value = comboBox->currentData();
            } else {
                QLineEdit *lineEdit = qobject_cast<QLineEdit*>(control);
                if (lineEdit) param.value = lineEdit->text();
            }
        }
    }
}

void ConfigEditor::updateControlsFromParameters()
{
    for (const auto& param : parameters) {
        if (controlMap.contains(param.name)) {
            QWidget *control = controlMap[param.name];

            if (param.type == "bool") {
                QCheckBox *checkBox = qobject_cast<QCheckBox*>(control);
                if (checkBox) checkBox->setChecked(param.value.toInt() == 1);
            } else if (param.type == "int") {
                QSpinBox *spinBox = qobject_cast<QSpinBox*>(control);
                if (spinBox) spinBox->setValue(param.value.toInt());
            } else if (param.type == "double") {
                QDoubleSpinBox *doubleSpinBox = qobject_cast<QDoubleSpinBox*>(control);
                if (doubleSpinBox) doubleSpinBox->setValue(param.value.toDouble());
            } else if (param.type == "enum") {
                QComboBox *comboBox = qobject_cast<QComboBox*>(control);
                if (comboBox) {
                    for (int i = 0; i < comboBox->count(); ++i) {
                        if (comboBox->itemData(i) == param.value) {
                            comboBox->setCurrentIndex(i);
                            break;
                        }
                    }
                }
            } else {
                QLineEdit *lineEdit = qobject_cast<QLineEdit*>(control);
                if (lineEdit) lineEdit->setText(param.value.toString());
            }
        }
    }
}

void ConfigEditor::on_btnOpen_clicked()
{
    QString filePath = QFileDialog::getOpenFileName(this, "打开INI文件", ".", "INI Files (*.ini);;All Files (*)");
    if (!filePath.isEmpty()) {
        currentFilePath = filePath;
        if (loadIniFile(filePath)) {
            setWindowTitle("INI配置工具 - " + filePath);
            QMessageBox::information(this, "成功", "文件加载成功");
        } else {
            QMessageBox::warning(this, "失败", "无法加载文件");
        }
    }
}

void ConfigEditor::on_btnSave_clicked()
{
    QString filePath = currentFilePath;
    if (filePath.isEmpty()) {
        filePath = QFileDialog::getSaveFileName(this, "保存INI文件", ".", "INI Files (*.ini);;All Files (*)");
    }

    if (!filePath.isEmpty()) {
        // 添加标志防止重复保存
        static bool isSaving = false;
        if (isSaving) return;
        isSaving = true;

        bool success = saveIniFile(filePath);
        if (success) {
            currentFilePath = filePath;
            setWindowTitle("INI配置工具 - " + filePath);
            QMessageBox::information(this, "成功", "文件保存成功");
        } else {
            QMessageBox::warning(this, "失败", "无法保存文件");
        }

        isSaving = false;
    }
}

七、创建UI文件(configeditor.ui)

js 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>ConfigEditor</class>
 <widget class="QWidget" name="ConfigEditor">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>900</width>
    <height>700</height>
   </rect>
  </property>
  <layout class="QVBoxLayout" name="verticalLayout">
   <item>
    <widget class="QScrollArea" name="scrollArea">
     <property name="widgetResizable">
      <bool>true</bool>
     </property>
     <widget class="QWidget" name="scrollAreaWidgetContents">
      <property name="geometry">
       <rect>
        <x>0</x>
        <y>0</y>
        <width>880</width>
        <height>600</height>
       </rect>
      </property>
     </widget>
    </widget>
   </item>
   <item>
    <widget class="QDialogButtonBox" name="buttonBox">
     <property name="standardButtons">
      <set>QDialogButtonBox::Open|QDialogButtonBox::Save</set>
     </property>
     <button class="QPushButton" name="btnOpen">
      <property name="text">
       <string>打开</string>
      </property>
     </button>
     <button class="QPushButton" name="btnSave">
      <property name="text">
       <string>保存</string>
      </property>
     </button>
    </widget>
   </item>
  </layout>
 </widget>
 <layoutdefault spacing="6" margin="11"/>
 <resources/>
 <connections/>
</ui>
相关推荐
熟悉的新风景33 分钟前
springboot项目或其他项目使用@Test测试项目接口配置-spring-boot-starter-test
java·spring boot·后端
岁忧2 小时前
(nice!!!)(LeetCode 面试经典 150 题 ) 30. 串联所有单词的子串 (哈希表+字符串+滑动窗口)
java·c++·leetcode·面试·go·散列表
晴空月明2 小时前
分布式系统高可用性设计 - 监控与日志系统
后端
zhangzhangkeji2 小时前
(34)总结记录下 Qt 里的事件类型,这是定义在 QEvent 这个基类里的枚举量 enum QEvent :: Type
qt
SunkingYang2 小时前
MFC/C++语言怎么比较CString类型最后一个字符
c++·mfc·cstring·子串·最后一个字符·比较
界面开发小八哥2 小时前
MFC扩展库BCGControlBar Pro v36.2新版亮点:可视化设计器升级
c++·mfc·bcg·界面控件·ui开发
R-G-B2 小时前
【15】MFC入门到精通——MFC弹窗提示 MFC关闭对话框 弹窗提示 MFC按键触发 弹窗提示
c++·mfc·mfc弹窗提示·mfc关闭弹窗提示·mfc按键触发 弹窗提示
十秒耿直拆包选手2 小时前
Qt:QCustomPlot类介绍
c++·qt·qcustomplot
珊瑚里的鱼3 小时前
第十三讲 | map和set的使用
开发语言·c++·笔记·visualstudio·visual studio
逑之3 小时前
C++笔记1:命名空间,缺省参数,引用等
开发语言·c++·笔记