如何在退出Qt时保存用户配置
一、简介
在我们使用 Qt
进行编写软件的时候,相必都会有一个疑惑,就是怎么在退出的时候对用户配置信息进行保存,这样下次在打开软件的时候,不需要重新配置,大大提升用户体验。
这个想法起源于 正点原子 XCOM 软件,这里我粘贴出链接:
【正点原子】XCOM串口调试助手软件V2.6版本发布,使用方便广泛的串口调试助手-OpenEdv-开源电子网
如点击无法进行跳转,可以自行复制以下链接进行手动跳转:
- http://www.openedv.com/thread-279749-1-1.html
这里对该软件进行简单介绍,其是一个用于显示 串口信息 的上位机工具,在使用过程中,当我们配置好波特率等信息以后,退出再次启用的时候,仍然会恢复成我们退出之前的配置信息。其界面如下所示:
那么基于这样的设定,我们不止可以用在上位机中,也可以用在其他 需要保存用户配置数据的场景中。因此,我写了这篇文章,用于介绍 如何在Qt退出时保存用户配置数据。
通过本文,你将学会以下内容:
- 学会在
Qt
程序退出时,保存用户配置数据信息。 - 在打包情况下,如何更好的保存数据
那么在开始之前,这里我先介绍以下我所使用的开发环境:
- Windows 10 x64
- Qt 5.12.3 (MinGw 32bit)
二、 保存配置数据(方法一)
在此方法中,我们将采取 ini
文件的形式对用户配置信息进行保存,这种形式的好处在于直观,且用户可修改。
在开始之前,我这对采取的 UI
进行简单描述,如下所示:
在两种方法中,将均采用此 UI
进行开发演示!!!
可以从图中看到,我仿照 正点原子 XCOM 对串口配置部分进行绘制显示。因此,我们需要 在退出的时候保存五个下拉复选框中的配置信息。
值得注意的是,对波特率下拉复选框的处理中,我并没有直接在
UI
中添加数据,而是通过代码函数进行添加的,而停止位、数据位、校验位均在UI
中完成添加。其实效果都一样,本文重点不在此处,只是给大家提供一种新的思路。
那么,我这里建立项目结构如下所示:
在前面的讲解中,已经对 quitsave.ui
进行了配置,接下来将书写实现代码。
2.1 项目实现
-
在
quitsave.h
中书写如下所示代码:#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
-
在
quitsave.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()); // 保存校验位 }
对于上述代码中,构造部分有以下代码:
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
文件的内容如下所示:
[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
文件的具体信息,大家可以参考以下文章:如点击无法进行跳转,可以自行复制以下链接进行手动跳转:
- https://blog.csdn.net/first_bug/article/details/129693732
这里提醒大家一下,该文件默认存放在编译输出文件中!!!例如,我采用的是 MinGW 32bit Debug
进行编译,则其文件位于:
大家想放在其他位置,也可以自行通过 QSettings
构造函数进行实现。
因此,在我们写入 ini
文件的时候采用如下所示的形式:
mySettings->setValue("/com/comx", ui->comboBox->currentData());
其中我们通过 com
节,访问到其中的 comx
键,对 comx
键进行赋值。所以其路径也就是 /com/comx
。
同样的在我们读取的时候也是通过这样的形式进行实现,如下所示:
mySettings->value("/com/comx")
需要知道的是,无论是写入还是读取,其传入、传出的参数类型均为 QVariant
,该类型能够存储和传递各种类型的数据,对于没有的数据类型,也可以自己进行定义。因此,在我们实际使用的过程中,一般进行以下形式的实现:
QString comx = mySettings->value("/com/comx").toString();
通过使用 toString()
方法将存储的数据转成 QString
类型。
有关
QVariant
的相关内容,大家可以参考如下所示的文章:如点击无法进行跳转,可以自行复制以下链接进行手动跳转:
- https://blog.csdn.net/xiaopei_yan/article/details/81410092
通过这种实现形式,我们使用 Qt
打包工具进行简单打包,得到以下文件结构:
注意需要将 config.ini
文件放到 quitSave.exe
文件的同一目录中。
但这种方法也有缺点,其可以被用户进行随意修改,而且不利于存储敏感数据。有的朋友还会选择使用 Enigma Virtual Box
进行二次打包成单独的 .exe
文件。我这里尝试过,进行二次打包之后,ini
文件的作用将失效,即无法保存用户配置信息。但这种做法存在着一定的好处,那就是更为直观,在我们调试过程中也更加方便。
2.2 运行结果
使用 Qt
进行打包之后运行效果如下所示:
可以看到,在初次对 串口设备 以及 波特率 进行设置之后,再次启动软件,其已经设置为我们退出之前的状态。
三、保存配置数据(方法二)
另一种方法仍然采用 QSettings
类进行操作,不同的是,我们不再使用 ini
文件进行存储。
这里 ui
设计与 方法一 中相同,文件结构也相同,这里我就不再赘述了,直接开始书写代码。
3.1 项目实现
-
在
quitsave.h
中书写如下所示代码:#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
该文件与 方法一 中文件没有什么不同。
-
在
quitsave.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()); // 保存校验位 }
根据上述代码所示,我们可以看到,其在类的构造上有以下内容:
mySerialPort = new QSerialPort;
// mySettings = new QSettings("config.ini", QSettings::IniFormat);
mySettings = new QSettings("MyConfig", "QuitSave");
// ui 初始化
ui_Init();
// 根据配置设置 UI
loadConfig();
这里依旧使用 QSettings
类,但不同的是,这里不再新建 .ini
文件。而使用 应用程序的组织名称 和 应用程序名称 进行初始化,其原始结构如下所示:
QSettings settings("QrganizationName", "ApplicationName");
其中,QrganizationName
表示 应用程序的组织名称,而 ApplicationName
表示 应用程序名称。看不懂的朋友也没有关系,我将会在后面对这部分进行解释。
这里的写法与使用 ini
文件保存的写法不同,其保存数据采用:
mySettings->setValue("com", ui->comboBox->currentData());
这里就没有 节 的概念,而我们直接定义一个 名称 即可。
同时,对于数据的读取操作,如下所示:
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 的时候保用户配置信息,并提供了两种解决方案供大家参考。
本文中的代码后续会逐步开源,欢迎关注,敬请期待!!!
欢迎广大读者提出问题以及修改意见,本人看到后会给予回应,欢迎留言,后续会逐步进行开源!!!
另外,由于文章是作者手打的文字,有些地方可能文字会出错,望谅解,也可私信联系我,我对其进行更改。
-
个人CSDN账号:刘梓谦_-CSDN博客
-
GitHub:Jiahao-Liu29 (github.com)