如何在退出Qt时保存用户配置

如何在退出Qt时保存用户配置

文章目录

一、简介

在我们使用 Qt 进行编写软件的时候,相必都会有一个疑惑,就是怎么在退出的时候对用户配置信息进行保存,这样下次在打开软件的时候,不需要重新配置,大大提升用户体验。

这个想法起源于 ++正点原子 XCOM++ 软件,这里我粘贴出链接:

【正点原子】XCOM串口调试助手软件V2.6版本发布,使用方便广泛的串口调试助手-OpenEdv-开源电子网

如点击无法进行跳转,可以自行复制以下链接进行手动跳转:

这里对该软件进行简单介绍,其是一个用于显示 串口信息 的上位机工具,在使用过程中,当我们配置好波特率等信息以后,退出再次启用的时候,仍然会恢复成我们退出之前的配置信息。其界面如下所示:

那么基于这样的设定,我们不止可以用在上位机中,也可以用在其他 ++需要保存用户配置数据的场景中++ 。因此,我写了这篇文章,用于介绍 如何在Qt退出时保存用户配置数据

通过本文,你将学会以下内容:

  1. 学会在 Qt 程序退出时,保存用户配置数据信息。
  2. 在打包情况下,如何更好的保存数据

那么在开始之前,这里我先介绍以下我所使用的开发环境:

  • Windows 10 x64
  • Qt 5.12.3 (MinGw 32bit)

二、 保存配置数据(方法一)

在此方法中,我们将采取 ini 文件的形式对用户配置信息进行保存,这种形式的好处在于直观,且用户可修改。

在开始之前,我这对采取的 UI 进行简单描述,如下所示:

在两种方法中,将均采用此 UI 进行开发演示!!!

可以从图中看到,我仿照 ++正点原子 XCOM++ 对串口配置部分进行绘制显示。因此,我们需要 在退出的时候保存五个下拉复选框中的配置信息

值得注意的是,对波特率下拉复选框的处理中,我并没有直接在 UI 中添加数据,而是通过代码函数进行添加的,而停止位、数据位、校验位均在 UI 中完成添加。

其实效果都一样,本文重点不在此处,只是给大家提供一种新的思路。

那么,我这里建立项目结构如下所示:

在前面的讲解中,已经对 quitsave.ui 进行了配置,接下来将书写实现代码。

2.1 项目实现

  1. quitsave.h 中书写如下所示代码:

    cpp 复制代码
    #ifndef QUITSAVE_H
    #define QUITSAVE_H
    
    #include <QWidget>
    
    #include <QSettings>
    
    #include <QtSerialPort/QSerialPort>
    #include <QtSerialPort/QSerialPortInfo>
    
    namespace Ui {
    class QuitSave;
    }
    
    class QuitSave : public QWidget
    {
        Q_OBJECT
    
    public:
        explicit QuitSave(QWidget *parent = nullptr);
        ~QuitSave();
    
    private:
        Ui::QuitSave *ui;
    
        // 串口对象
        QSerialPort* mySerialPort;
        // QSettings 对象
        QSettings*   mySettings;
    
        /* ui 初始化 */
        void ui_Init(void);
    
        void loadConfig(void);
    
        /* 保存配置 */
        void saveConfig(void);
    };
    
    #endif // QUITSAVE_H
  2. quitsave.cpp 中进行如下实现:

    cpp 复制代码
    #include "quitsave.h"
    #include "ui_quitsave.h"
    
    #include <QDebug>
    
    QuitSave::QuitSave(QWidget *parent) :
        QWidget(parent),
        ui(new Ui::QuitSave)
    {
        ui->setupUi(this);
    
        mySerialPort = new QSerialPort;
    
        mySettings = new QSettings("config.ini", QSettings::IniFormat);
    
        // ui 初始化
        ui_Init();
    
        // 根据配置设置 UI
        loadConfig();
    }
    
    QuitSave::~QuitSave()
    {
        // 保存配置项
        saveConfig();
    
        delete mySettings;
    
        delete mySerialPort;
    
        delete ui;
    }
    
    /*!
     *  @File        : quitsave.cpp
     *  @Brief       : UI 初始化函数
     *  @Details     : None
     *  @Param       : void
     *  @Return      : void
     *  @Author      : Liu Jiahao
     *  @Date        : 2024-08-26 10:35:52
     *  @Version     : v1.1
     *  @Copyright   : Copyright By Liu Jiahao, All Rights Reserved
     *
     */
    void QuitSave::ui_Init()
    {
        // 获取串口端口
        foreach(const QSerialPortInfo &info, QSerialPortInfo::availablePorts())
        {
            ui->comboBox->addItem(info.portName() + ":"+ info.description(), info.portName());  // 将端口号以及设备描述信息写入UI
        }
    
        // 添加典型波特率
        foreach (auto baudRate, QSerialPortInfo::standardBaudRates()) {
            ui->baundComboBox->addItem(QString::number(baudRate, 10), QString::number(baudRate, 10));
        }
    
    }
    
    /*!
     *  @File        : quitsave.cpp
     *  @Brief       : 加载配置信息
     *  @Details     : None
     *  @Param       : void
     *  @Return      : void
     *  @Author      : Liu Jiahao
     *  @Date        : 2024-08-26 13:11:45
     *  @Version     : v1.1
     *  @Copyright   : Copyright By Liu Jiahao, All Rights Reserved
     *
     */
    void QuitSave::loadConfig()
    {
        QString comx = mySettings->value("/com/comx").toString();           // 获取 COM 接口
        QString baund = mySettings->value("/com/baund").toString();         // 获取 baund 接口
        QString stop = mySettings->value("/com/stop").toString();           // 获取 stop 接口
        QString data = mySettings->value("/com/data").toString();           // 获取 data 接口
        QString check = mySettings->value("/com/check").toString();         // 获取 check 接口
    
        /* 获取设备列表 */
        int count = ui->comboBox->count();                                  // 获取所有元素
        int index = 0;
        for(index = 0; index < count; index++) {
            if (ui->comboBox->itemData(index).toString() == comx)
                break;
        }
    
        /* 刷新 UI */
        ui->comboBox->setCurrentIndex(index == count ? 0 : index);
        ui->baundComboBox->setCurrentText(baund);
        ui->stopComboBox->setCurrentText(stop);
        ui->dataComboBoBox->setCurrentText(data);
        ui->checkComboBox->setCurrentText(check);
    }
    
    /*!
     *  @File        : quitsave.cpp
     *  @Brief       : 保存配置文件
     *  @Details     : None
     *  @Param       : void
     *  @Return      : void
     *  @Author      : Liu Jiahao
     *  @Date        : 2024-08-26 11:20:02
     *  @Version     : v1.1
     *  @Copyright   : Copyright By Liu Jiahao, All Rights Reserved
     *
     */
    void QuitSave::saveConfig()
    {
        // 将读取的内容进行保存
        mySettings->setValue("/com/comx", ui->comboBox->currentData());                         // 保存端口号
        mySettings->setValue("/com/baund", ui->baundComboBox->currentText());                   // 保存波特率
        mySettings->setValue("/com/stop", ui->stopComboBox->currentText());                     // 保存停止位
        mySettings->setValue("/com/data", ui->dataComboBoBox->currentText());                   // 保存数据位
        mySettings->setValue("/com/check", ui->checkComboBox->currentText());                   // 保存校验位
    }

对于上述代码中,构造部分有以下代码:

cpp 复制代码
mySerialPort = new QSerialPort;

mySettings = new QSettings("config.ini", QSettings::IniFormat);

// ui 初始化
ui_Init();

// 根据配置设置 UI
loadConfig();

其中,创建了 QSerialPort 对象和 QSettings 对象,我们实现用户配置信息的保存也是基于 QSettings 进行的。

值得注意的是,我们通过 QSettings 创建了一个 config.ini 文件,用于保存配置信息。如果该文件,则直接可以进行读取和写入,否则将会新创建文件。

随后对 UI 进行初始化,这部分代码主要是实现 ++串口设备检测和串口典型波特率写入++。具体实现内容可以在上述代码中找到。

最后对 UI 进行设置,加载关闭之前的用户配置信息。在该函数中,也是通过读取 ini 文件完成对 UI 的操作。

而在析构部分,通过调用 saveConfig() 函数实现了关键信息的保存。其函数内部也是通过写入 ini 文件得以实现的。

那么,说了很多,ini 文件中到底有什么呢?接下来我将解答大家的疑惑,其中 config.ini 文件的内容如下所示:

ini 复制代码
[com]
comx=COM58
baund=115200
stop=1
data=8
check=None

ini 文件是 initialization file 的缩写,即 初始化文件 ,是 widows 系统配置文件所采用的存储格式。而对于以上内容,例如 comx=COM58 叫做参数 ,其存在的结构我们称之为 键值对 ,即 comx 为键,COM58 为值。相信学过 JSON、C++ 的朋友都很熟悉,这里我们也可以 ++简单理解为一个变量对应一个具体的值++ 。而 [com] 叫做 ,它将所有的键值对结合在一起。在我们的例子中,comx、baund、stop、data、check 都属 com 这个节中的参数。

有关 ini 文件的具体信息,大家可以参考以下文章:

ini配置文件_ini文件-CSDN博客

如点击无法进行跳转,可以自行复制以下链接进行手动跳转:

这里提醒大家一下,该文件默认存放在编译输出文件中 !!!例如,我采用的是 MinGW 32bit Debug 进行编译,则其文件位于:

++大家想放在其他位置,也可以自行通过 QSettings 构造函数进行实现。++

因此,在我们写入 ini 文件的时候采用如下所示的形式:

cpp 复制代码
mySettings->setValue("/com/comx", ui->comboBox->currentData());

其中我们通过 com 节,访问到其中的 comx 键,对 comx 键进行赋值。所以其路径也就是 /com/comx

同样的在我们读取的时候也是通过这样的形式进行实现,如下所示:

cpp 复制代码
mySettings->value("/com/comx")

需要知道的是,无论是写入还是读取,其传入、传出的参数类型均为 QVariant,该类型能够存储和传递各种类型的数据,对于没有的数据类型,也可以自己进行定义。因此,在我们实际使用的过程中,一般进行以下形式的实现:

cpp 复制代码
QString comx = mySettings->value("/com/comx").toString();  

通过使用 toString() 方法将存储的数据转成 QString 类型。

有关 QVariant 的相关内容,大家可以参考如下所示的文章:

QVariant的用法-CSDN博客

如点击无法进行跳转,可以自行复制以下链接进行手动跳转:

通过这种实现形式,我们使用 Qt 打包工具进行简单打包,得到以下文件结构:

注意需要将 config.ini 文件放到 quitSave.exe 文件的同一目录中。

但这种方法也有缺点 ,其可以被用户进行随意修改,而且不利于存储敏感数据。有的朋友还会选择使用 Enigma Virtual Box 进行二次打包成单独的 .exe 文件。我这里尝试过,进行二次打包之后,ini 文件的作用将失效,即无法保存用户配置信息。但这种做法存在着一定的好处,那就是更为直观,在我们调试过程中也更加方便。

2.2 运行结果

使用 Qt 进行打包之后运行效果如下所示:

可以看到,在初次对 串口设备 以及 波特率 进行设置之后,再次启动软件,其已经设置为我们退出之前的状态。


三、保存配置数据(方法二)

另一种方法仍然采用 QSettings 类进行操作,不同的是,我们不再使用 ini 文件进行存储。

这里 ui 设计与 ++方法一++ 中相同,文件结构也相同,这里我就不再赘述了,直接开始书写代码。

3.1 项目实现

  1. quitsave.h 中书写如下所示代码:

    cpp 复制代码
    #ifndef QUITSAVE_H
    #define QUITSAVE_H
    
    #include <QWidget>
    
    #include <QSettings>
    
    #include <QtSerialPort/QSerialPort>
    #include <QtSerialPort/QSerialPortInfo>
    
    namespace Ui {
    class QuitSave;
    }
    
    class QuitSave : public QWidget
    {
        Q_OBJECT
    
    public:
        explicit QuitSave(QWidget *parent = nullptr);
        ~QuitSave();
    
    private:
        Ui::QuitSave *ui;
    
        // 串口对象
        QSerialPort* mySerialPort;
        // QSettings 对象
        QSettings*   mySettings;
    
        /* ui 初始化 */
        void ui_Init(void);
    
        void loadConfig(void);
    
        /* 保存配置 */
        void saveConfig(void);
    };
    
    #endif // QUITSAVE_H

    该文件与 ++方法一++ 中文件没有什么不同。

  2. quitsave.cpp 中进行如下实现:

    cpp 复制代码
    #include "quitsave.h"
    #include "ui_quitsave.h"
    
    #include <QDebug>
    
    QuitSave::QuitSave(QWidget *parent) :
        QWidget(parent),
        ui(new Ui::QuitSave)
    {
        ui->setupUi(this);
    
        mySerialPort = new QSerialPort;
    
    //    mySettings = new QSettings("config.ini", QSettings::IniFormat);
        mySettings = new QSettings("MyConfig", "QuitSave");
    
        // ui 初始化
        ui_Init();
    
        // 根据配置设置 UI
        loadConfig();
    }
    
    QuitSave::~QuitSave()
    {
        // 保存配置项
        saveConfig();
    
        delete mySettings;
    
        delete mySerialPort;
    
        delete ui;
    }
    
    /*!
     *  @File        : quitsave.cpp
     *  @Brief       : UI 初始化函数
     *  @Details     : None
     *  @Param       : void
     *  @Return      : void
     *  @Author      : Liu Jiahao
     *  @Date        : 2024-08-26 10:35:52
     *  @Version     : v1.1
     *  @Copyright   : Copyright By Liu Jiahao, All Rights Reserved
     *
     */
    void QuitSave::ui_Init()
    {
        // 获取串口端口
        foreach(const QSerialPortInfo &info, QSerialPortInfo::availablePorts())
        {
            ui->comboBox->addItem(info.portName() + ":"+ info.description(), info.portName());  // 将端口号以及设备描述信息写入UI
        }
    
        // 添加典型波特率
        foreach (auto baudRate, QSerialPortInfo::standardBaudRates()) {
            ui->baundComboBox->addItem(QString::number(baudRate, 10), QString::number(baudRate, 10));
        }
    
    }
    
    /*!
     *  @File        : quitsave.cpp
     *  @Brief       : 加载配置信息
     *  @Details     : None
     *  @Param       : void
     *  @Return      : void
     *  @Author      : Liu Jiahao
     *  @Date        : 2024-08-26 13:11:45
     *  @Version     : v1.1
     *  @Copyright   : Copyright By Liu Jiahao, All Rights Reserved
     *
     */
    void QuitSave::loadConfig()
    {
        QString comx = mySettings->value("com").toString();            // 获取 COM 接口
        QString baund = mySettings->value("baund").toString();         // 获取 baund 接口
        QString stop = mySettings->value("stop").toString();           // 获取 stop 接口
        QString data = mySettings->value("data").toString();           // 获取 data 接口
        QString check = mySettings->value("check").toString();         // 获取 check 接口
    
        /* 获取设备列表 */
        int count = ui->comboBox->count();                                  // 获取所有元素
        int index = 0;
        for(index = 0; index < count; index++) {
            if (ui->comboBox->itemData(index).toString() == comx)
                break;
        }
    
        /* 刷新 UI */
        ui->comboBox->setCurrentIndex(index == count ? 0 : index);
        ui->baundComboBox->setCurrentText(baund);
        ui->stopComboBox->setCurrentText(stop);
        ui->dataComboBoBox->setCurrentText(data);
        ui->checkComboBox->setCurrentText(check);
    }
    
    /*!
     *  @File        : quitsave.cpp
     *  @Brief       : 保存配置文件
     *  @Details     : None
     *  @Param       : void
     *  @Return      : void
     *  @Author      : Liu Jiahao
     *  @Date        : 2024-08-26 11:20:02
     *  @Version     : v1.1
     *  @Copyright   : Copyright By Liu Jiahao, All Rights Reserved
     *
     */
    void QuitSave::saveConfig()
    {
        // 将读取的内容并保存
        mySettings->setValue("com", ui->comboBox->currentData());                          // 保存端口号
        mySettings->setValue("baund", ui->baundComboBox->currentText());                   // 保存波特率
        mySettings->setValue("stop", ui->stopComboBox->currentText());                     // 保存停止位
        mySettings->setValue("data", ui->dataComboBoBox->currentText());                   // 保存数据位
        mySettings->setValue("check", ui->checkComboBox->currentText());                   // 保存校验位
    }

根据上述代码所示,我们可以看到,其在类的构造上有以下内容:

cpp 复制代码
mySerialPort = new QSerialPort;

//    mySettings = new QSettings("config.ini", QSettings::IniFormat);
mySettings = new QSettings("MyConfig", "QuitSave");

// ui 初始化
ui_Init();

// 根据配置设置 UI
loadConfig();

这里依旧使用 QSettings 类,但不同的是,这里不再新建 .ini 文件。而使用 ++应用程序的组织名称++ 和 ++应用程序名称++ 进行初始化,其原始结构如下所示:

cpp 复制代码
QSettings settings("QrganizationName", "ApplicationName");

其中,QrganizationName 表示 ++应用程序的组织名称++ ,而 ApplicationName 表示 ++应用程序名称++。看不懂的朋友也没有关系,我将会在后面对这部分进行解释。

这里的写法与使用 ini 文件保存的写法不同,其保存数据采用:

cpp 复制代码
mySettings->setValue("com", ui->comboBox->currentData()); 

这里就没有 的概念,而我们直接定义一个 ++名称++ 即可。

同时,对于数据的读取操作,如下所示:

cpp 复制代码
QString comx = mySettings->value("com").toString(); 

因此,直接通过 ++名称++ 读取即可。而由于返回的是 QVariant 类型,因此需要 toString() 处理。

那么会有朋友好奇,通过这种方法构建,是如何保存数据的。其实 QSettings 不仅可以读写 ini 文件,也可以对 注册表 进行读写。这里我们就是使用了 读写注册表 的形式。

其在注册表中的位置为:HKEY_CURRENT_USER\SOFTWARE\<QrganizationName>\<ApplicationName> 中。在我的示例中,QrganizationName 我定义为 MyConfig,而 ApplicationName 我定义为 QuitSave。其存储位置就在上面提到的注册表中,如下所示:

正式因为如此,我们才不需要创建任何文件,就可以实现参数的读写操作。在首次进入软件的时候,该注册表不存在,通过 new 一个 QSettings 对象的时候,会自动创建相应的注册表。我们通过访问对应位置的注册表即可看到我们读写的信息。

这种方法的好处在于隐藏了我们的保存配置文件,且通过二次打包之后仍然能够正常使用。目前据我所知,这也是大多数软件采取的方式,其他方式,欢迎大佬指出。另外,对于账号密码等敏感信息最好不要采取这种方式,毕竟注册表也并非不可见。

3.2 运行结果

在二次打包之后运行效果如下所示:

可以看到,在初次对 串口设备 以及 波特率 进行设置之后,再次启动软件,其已经设置为我们退出之前的状态。


四、写在最后

本文介绍了 如何在退出 Qt 的时候保用户配置信息,并提供了两种解决方案供大家参考

本文中的代码后续会逐步开源,欢迎关注,敬请期待!!!

欢迎广大读者提出问题以及修改意见,本人看到后会给予回应,欢迎留言,后续会逐步进行开源!!!

另外,由于文章是作者手打的文字,有些地方可能文字会出错,望谅解,也可私信联系我,我对其进行更改。

相关推荐
XiaoLeisj1 小时前
【JavaEE初阶 — 多线程】单例模式 & 指令重排序问题
java·开发语言·java-ee
励志成为嵌入式工程师2 小时前
c语言简单编程练习9
c语言·开发语言·算法·vim
捕鲸叉2 小时前
创建线程时传递参数给线程
开发语言·c++·算法
A charmer2 小时前
【C++】vector 类深度解析:探索动态数组的奥秘
开发语言·c++·算法
Peter_chq2 小时前
【操作系统】基于环形队列的生产消费模型
linux·c语言·开发语言·c++·后端
记录成长java4 小时前
ServletContext,Cookie,HttpSession的使用
java·开发语言·servlet
前端青山4 小时前
Node.js-增强 API 安全性和性能优化
开发语言·前端·javascript·性能优化·前端框架·node.js
睡觉谁叫~~~4 小时前
一文解秘Rust如何与Java互操作
java·开发语言·后端·rust
音徽编程4 小时前
Rust异步运行时框架tokio保姆级教程
开发语言·网络·rust
观音山保我别报错4 小时前
C语言扫雷小游戏
c语言·开发语言·算法