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